Ticket #3205: t3205_enlighten_too_dark_chat_colors_v2.patch

File t3205_enlighten_too_dark_chat_colors_v2.patch, 16.0 KB (added by elexis, 9 years ago)

Created the ensureMinimumLightness function. Moved player color values and functions to color.js, so that it can be used in other pages too. Moved the hardcoded color values used only in the lobby to the beginning of lobby.js Removed most of fixedColors since they were not used anywhere (which also allowed the removal of the hexToRgb and r function).

  • binaries/data/mods/public/gui/common/color.js

     
     1//Some names are special and should always appear in certain colors.
     2var g_FixedPlayerColors = {
     3    "system": repeatString(7, "255.0.0."),
     4    "@WFGbot": repeatString(7, "255.24.24."),
     5    "pyrogenesis": repeatString(2, "97.0.0.") + repeatString(2, "124.0.0.") + "138.0.0." +
     6                    repeatString(2, "174.0.0.") + repeatString(2, "229.40.0.") + repeatString(2, "243.125.15.")
     7};
     8
     9function colorizePlayername(playername)
     10{
     11    let color = g_FixedPlayerColors[playername];
     12    if (color) {
     13        color = color.split(".");
     14        return ('[color="' + playername.split("").map(function (c, i) color.slice(i * 3, i * 3 + 3).join(" ") + '"]' + c + '[/color][color="')
     15                .join("") + '"]').slice(0, -10);
     16    }
     17    return '[color="' + getPlayerColor(playername.replace(g_modPrefix, "")) + '"]' + playername + '[/color]';
     18}
     19
     20// Generate a (mostly) unique color for this player based on their name.
     21// See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
     22function getPlayerColor(playername)
     23{
     24    // Generate a probably-unique hash for the player name and use that to create a color.
     25    let hash = 0;
     26    for (let i = 0; i < playername.length; i++)
     27        hash = playername.charCodeAt(i) + ((hash << 5) - hash);
     28
     29    // First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
     30    // The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
     31    // us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
     32    // we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
     33    let [h, s, l] = ensureMinimumLightness(rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF));
     34    return hslToRgb(h, s, l).join(" ");
     35}
     36
     37function ensureMinimumLightness(hsl)
     38{
     39    let [h, s, l] = hsl;
     40    return [h, s, Math.max(0.7, l)];
     41}
     42
     43//Ensure `value` is between 0 and 1.
     44function clampColorValue(value)
     45{
     46    return Math.abs(1 - Math.abs(value - 1));
     47}
     48
     49// See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
     50function rgbToHsl(r, g, b)
     51{
     52    r /= 255;
     53    g /= 255;
     54    b /= 255;
     55    let max = Math.max(r, g, b), min = Math.min(r, g, b);
     56    let h, s, l = (max + min) / 2;
     57
     58    if (max == min)
     59        h = s = 0; // achromatic
     60    else
     61    {
     62        let d = max - min;
     63        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
     64        switch (max)
     65        {
     66            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
     67            case g: h = (b - r) / d + 2; break;
     68            case b: h = (r - g) / d + 4; break;
     69        }
     70        h /= 6;
     71    }
     72
     73    return [h, s, l];
     74}
     75
     76function hslToRgb(h, s, l)
     77{
     78    function hue2rgb(p, q, t)
     79    {
     80        if (t < 0) t += 1;
     81        if (t > 1) t -= 1;
     82        if (t < 1/6) return p + (q - p) * 6 * t;
     83        if (t < 1/2) return q;
     84        if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
     85        return p;
     86    }
     87
     88    [h, s, l] = [h, s, l].map(clampColorValue);
     89    let r, g, b;
     90
     91    if (s == 0)
     92        r = g = b = l; // achromatic
     93    else {
     94        let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
     95        let p = 2 * l - q;
     96        r = hue2rgb(p, q, h + 1/3);
     97        g = hue2rgb(p, q, h);
     98        b = hue2rgb(p, q, h - 1/3);
     99    }
     100
     101    return [r, g, b].map(function (n) Math.round(n * 255));
     102}
     103
     104function repeatString(times, string) {
     105    return Array(times + 1).join(string);
     106}
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

     
    17121712        var mapData = loadMapData(mapName);
    17131713        var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
    17141714        var pData = mapSettings.PlayerData ? mapSettings.PlayerData[player] : {};
    17151715        var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[player] : {};
    17161716
    1717         color = rgbToGuiColor(getSetting(pData, pDefs, "Color"));
     1717        color = getSetting(pData, pDefs, "Color");
     1718
     1719        // enlighten colors to improve readability
     1720        let [h, s, l] = rgbToHsl(color.r, color.g, color.b);
     1721        let [r, g, b] = ensureMinimumLightness(hslToRgb(h, s, l));
     1722        color = rgbToGuiColor({"r": r, "g": g, "b": b});
    17181723    }
    17191724
    17201725    var formatted;
    17211726    switch (msg.type)
    17221727    {
  • binaries/data/mods/public/gui/gamesetup/gamesetup.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22
    33<objects>
    44
     5    <script file="gui/common/color.js"/>
    56    <script file="gui/common/network.js"/>
    67    <script file="gui/common/functions_civinfo.js"/>
    78    <script file="gui/common/functions_global_object.js"/>
    89    <script file="gui/common/functions_utility.js"/>
    910    <script file="gui/gamesetup/gamesetup.js"/>
  • binaries/data/mods/public/gui/lobby/lobby.js

     
    1616var g_modPrefix = "@";
    1717var g_joined = false;
    1818// Block spammers for 30 seconds.
    1919var SPAM_BLOCK_LENGTH = 30;
    2020
     21var g_ColorPresence = {
     22    "playing": "125 0 0",
     23    "gone": "229 76 13",
     24    "away": "229 76 13",
     25    "available": "0 219 0",
     26    "offline": "0 0 0",
     27    "unknown": "178 178 178"
     28};
     29//'waiting' games are highlighted in orange, 'running' in red, and 'init' in green.
     30var g_ColorGame = {
     31    "init": "0 219 0",
     32    "waiting": "255 127 0",
     33    "running": "219 0 0"
     34};
     35var g_ColorModerator = "0 219 0";
     36var g_ColorSystemMessage = "150 0 0";
     37
    2138////////////////////////////////////////////////////////////////////////////////////////////////
    2239
    2340function init(attribs)
    2441{
    2542    // Play menu music
     
    273290    if (role && caller == "lobbylist")
    274291    {
    275292        // Make the role uppercase.
    276293        role = role.charAt(0).toUpperCase() + role.slice(1);
    277294        if (role == "Moderator")
    278             role = '[color="0 125 0"]' + translate(role) + '[/color]';
     295            role = '[color="' + g_ColorModerator + '"]' + translate(role) + '[/color]';
    279296    }
    280297    else
    281298        role = "";
    282299
    283300    Engine.GetGUIObjectByName("usernameText").caption = user;
     
    457474    var c = 0;
    458475    for (var g of gameList)
    459476    {
    460477        if (!filterGame(g))
    461478        {
    462             // 'waiting' games are highlighted in orange, 'running' in red, and 'init' in green.
    463             let name = escapeText(g.name);
    464             if (g.state == 'init')
    465                 name = '[color="0 125 0"]' + name + '[/color]';
    466             else if (g.state == 'waiting')
    467                 name = '[color="255 127 0"]' + name + '[/color]';
    468             else
    469                 name = '[color="255 0 0"]' + name + '[/color]';
     479            let name = '[color="' + g_ColorGame[g.state] + '"]' + escapeText(g.name) + '[/color]';
    470480            list_name.push(name);
    471481            list_ip.push(g.ip);
    472482            list_mapName.push(translate(g.niceMapName));
    473483            list_mapSize.push(translatedMapSize(g.mapSize));
    474484            let idx = g_mapTypes.indexOf(g.mapType);
     
    504514 * @return Colorized versions of name, status, and rating.
    505515 */
    506516function formatPlayerListEntry(nickname, presence, rating)
    507517{
    508518    // Set colors based on player status
    509     var color;
    510     var status;
     519    let color = presence in g_ColorPresence ? g_ColorPresence[presence] : g_ColorPresence["unknown"];
     520    let status;
     521   
    511522    switch (presence)
    512523    {
    513524    case "playing":
    514         color = "125 0 0";
    515525        status = translate("Busy");
    516526        break;
    517527    case "gone":
    518528    case "away":
    519         color = "229 76 13";
    520529        status = translate("Away");
    521530        break;
    522531    case "available":
    523         color = "0 125 0";
    524532        status = translate("Online");
    525533        break;
    526534    case "offline":
    527         color = "0 0 0";
    528535        status = translate("Offline");
    529536        break;
    530537    default:
    531538        warn(sprintf("Unknown presence '%(presence)s'", { presence: presence }));
    532         color = "178 178 178";
    533539        status = translateWithContext("lobby presence", "Unknown");
    534540        break;
    535541    }
    536542    // Center the unrated symbol.
    537543    if (rating == "-")
     
    539545    var formattedStatus = '[color="' + color + '"]' + status + "[/color]";
    540546    var formattedRating = '[color="' + color + '"]' + rating + "[/color]";
    541547    var role = Engine.LobbyGetPlayerRole(nickname);
    542548    if (role == "moderator")
    543549        nickname = g_modPrefix + nickname;
    544     var formattedName = colorPlayerName(nickname);
     550    let formattedName = colorizePlayername(nickname);
    545551
    546552    // Push this player's name and status onto the list
    547553    return [formattedName, formattedStatus, formattedRating];
    548554}
    549555
     
    768774            break;
    769775        case "system":
    770776            switch (message.level)
    771777            {
    772778            case "standard":
    773                 addChatMessage({ "from": "system", "text": text, "color": "150 0 0" });
     779                addChatMessage({ "from": "system", "text": text, "color": g_ColorSystemMessage });
    774780                if (message.text == "disconnected")
    775781                {
    776782                    // Clear the list of games and the list of players
    777783                    updateGameList();
    778784                    updateLeaderboard();
     
    784790                {
    785791                    Engine.GetGUIObjectByName("hostButton").enabled = true;
    786792                }
    787793                break;
    788794            case "error":
    789                 addChatMessage({ "from": "system", "text": text, "color": "150 0 0" });
     795                addChatMessage({ "from": "system", "text": text, "color": g_ColorSystemMessage });
    790796                break;
    791797            case "internal":
    792798                switch (message.text)
    793799                {
    794800                case "gamelist updated":
     
    897903    if (!msg.datetime)
    898904        msg.datetime = null;
    899905
    900906    // Highlight local user's nick
    901907    if (msg.text.indexOf(g_Name) != -1 && g_Name != msg.from)
    902         msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Name + '\\b', "g"), colorPlayerName(g_Name));
     908        msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Name + '\\b', "g"), colorizePlayername(g_Name));
    903909
    904910    // Run spam test if it's not a historical message
    905911    if (!msg.datetime)
    906912        updateSpamMonitor(msg.from);
    907913    if (isSpam(msg.text, msg.from))
     
    938944 */
    939945function ircFormat(text, from, color, key, datetime)
    940946{
    941947    // Generate and apply color to uncolored names,
    942948    if (!color && from)
    943         var coloredFrom = colorPlayerName(from);
     949        var coloredFrom = colorizePlayername(from);
    944950    else if (color && from)
    945951        var coloredFrom = '[color="' + color + '"]' + from + "[/color]";
    946952
    947953    // Handle commands allowed past handleSpecialCommand.
    948954    if (text[0] == '/')
     
    10871093            stats[0] = 0;
    10881094        }
    10891095    }
    10901096
    10911097}
    1092 
    1093 /* Utilities */
    1094 // Generate a (mostly) unique color for this player based on their name.
    1095 // See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
    1096 function getPlayerColor(playername)
    1097 {
    1098     // Generate a probably-unique hash for the player name and use that to create a color.
    1099     var hash = 0;
    1100     for (var i = 0; i < playername.length; i++)
    1101         hash = playername.charCodeAt(i) + ((hash << 5) - hash);
    1102 
    1103     // First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
    1104     // The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
    1105     // us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
    1106     // we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
    1107     var [h, s, l] = rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF);
    1108     return hslToRgb(h, s, Math.max(0.4, l)).join(" ");
    1109 }
    1110 
    1111 function repeatString(times, string) {
    1112     return Array(times + 1).join(string);
    1113 }
    1114 
    1115 // Some names are special and should always appear in certain colors.
    1116 var fixedColors = { "system": repeatString(7, "255.0.0."), "@WFGbot": repeatString(7, "255.24.24."),
    1117                     "pyrogenesis": repeatString(2, "97.0.0.") + repeatString(2, "124.0.0.") + "138.0.0." +
    1118                         repeatString(2, "174.0.0.") + repeatString(2, "229.40.0.") + repeatString(2, "243.125.15.") };
    1119 function colorPlayerName(playername)
    1120 {
    1121     var color = fixedColors[playername];
    1122     if (color) {
    1123     color = color.split(".");
    1124     return ('[color="' + playername.split("").map(function (c, i) color.slice(i * 3, i * 3 + 3).join(" ") + '"]' + c + '[/color][color="')
    1125                 .join("") + '"]').slice(0, -10);
    1126     }
    1127     return '[color="' + getPlayerColor(playername.replace(g_modPrefix, "")) + '"]' + playername + '[/color]';
    1128 }
    1129 
    1130 // Ensure `value` is between 0 and 1.
    1131 function clampColorValue(value)
    1132 {
    1133     return Math.abs(1 - Math.abs(value - 1));
    1134 }
    1135 
    1136 // See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
    1137 function rgbToHsl(r, g, b)
    1138 {
    1139     r /= 255;
    1140     g /= 255;
    1141     b /= 255;
    1142     var max = Math.max(r, g, b), min = Math.min(r, g, b);
    1143     var h, s, l = (max + min) / 2;
    1144 
    1145     if (max == min)
    1146         h = s = 0; // achromatic
    1147     else
    1148     {
    1149         var d = max - min;
    1150         s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    1151         switch (max)
    1152         {
    1153             case r: h = (g - b) / d + (g < b ? 6 : 0); break;
    1154             case g: h = (b - r) / d + 2; break;
    1155             case b: h = (r - g) / d + 4; break;
    1156         }
    1157         h /= 6;
    1158     }
    1159 
    1160     return [h, s, l];
    1161 }
    1162 
    1163 function hslToRgb(h, s, l)
    1164 {
    1165     function hue2rgb(p, q, t)
    1166     {
    1167         if (t < 0) t += 1;
    1168         if (t > 1) t -= 1;
    1169         if (t < 1/6) return p + (q - p) * 6 * t;
    1170         if (t < 1/2) return q;
    1171         if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
    1172         return p;
    1173     }
    1174 
    1175     [h, s, l] = [h, s, l].map(clampColorValue);
    1176     var r, g, b;
    1177 
    1178     if (s == 0)
    1179         r = g = b = l; // achromatic
    1180     else {
    1181         var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    1182         var p = 2 * l - q;
    1183         r = hue2rgb(p, q, h + 1/3);
    1184         g = hue2rgb(p, q, h);
    1185         b = hue2rgb(p, q, h - 1/3);
    1186     }
    1187 
    1188     return [r, g, b].map(function (n) Math.round(n * 255));
    1189 }
    1190 
    1191 (function () {
    1192 function hexToRgb(hex) {
    1193     return parseInt(hex.slice(0, 2), 16) + "." + parseInt(hex.slice(2, 4), 16) + "." + parseInt(hex.slice(4, 6), 16) + ".";
    1194 }
    1195 function r(times, hex) {
    1196     return repeatString(times, hexToRgb(hex));
    1197 }
    1198 
    1199 fixedColors["Twilight_Sparkle"] = r(2, "d19fe3") + r(2, "b689c8") + r(2, "a76bc2") +
    1200     r(4, "263773") + r(2, "131f46") + r(2, "662d8a") + r(2, "ed438a");
    1201 fixedColors["Applejack"] = r(3, "ffc261") + r(3, "efb05d") + r(3, "f26f31");
    1202 fixedColors["Rarity"] = r(1, "ebeff1") + r(1, "dee3e4") + r(1, "bec2c3") +
    1203     r(1, "83509f") + r(1, "4b2568") + r(1, "4917d6");
    1204 fixedColors["Rainbow_Dash"] = r(2, "ee4144") + r(1, "f37033") + r(1, "fdf6af") +
    1205     r(1, "62bc4d") + r(1, "1e98d3") + r(2, "672f89") + r(1, "9edbf9") +
    1206     r(1, "88c4eb") + r(1, "77b0e0") + r(1, "1e98d3");
    1207 fixedColors["Pinkie_Pie"] = r(2, "f3b6cf") + r(2, "ec9dc4") + r(4, "eb81b4") +
    1208     r(1, "ed458b") + r(1, "be1d77");
    1209 fixedColors["Fluttershy"] = r(2, "fdf6af") + r(2, "fee78f") + r(2, "ead463") +
    1210     r(2, "f3b6cf") + r(2, "eb81b4");
    1211 fixedColors["Sweetie_Belle"] = r(2, "efedee") + r(3, "e2dee3") + r(3, "cfc8d1") +
    1212     r(2, "b28dc0") + r(2, "f6b8d2") + r(1, "795b8a");
    1213 fixedColors["Apple_Bloom"] = r(2, "f4f49b") + r(2, "e7e793") + r(2, "dac582") +
    1214     r(2, "f46091") + r(2, "f8415f") + r(1, "c52451");
    1215 fixedColors["Scootaloo"] = r(2, "fbba64") + r(2, "f2ab56") + r(2, "f37003") +
    1216     r(2, "bf5d95") + r(1, "bf1f79");
    1217 fixedColors["Luna"] = r(1, "7ca7fa") + r(1, "5d6fc1") + r(1, "656cb9") + r(1, "393993");
    1218 fixedColors["Celestia"] = r(1, "fdfafc") + r(1, "f7eaf2") + r(1, "d99ec5") +
    1219     r(1, "00aec5") + r(1, "f7c6dc") + r(1, "98d9ef") + r(1, "ced7ed") + r(1, "fed17b");
    1220 })();
  • binaries/data/mods/public/gui/lobby/lobby.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22
    33<objects>
    44    <script file="gui/common/functions_global_object.js"/>
    55    <script file="gui/common/functions_utility.js"/>
     6    <script file="gui/common/color.js"/>
    67    <script file="gui/common/timer.js"/>
    78    <script file="gui/common/music.js"/>
    8 
    99    <script file="gui/lobby/lobby.js"/>
    1010
    1111    <object type="image" style="ModernWindow" size="0 0 100% 100%" name="lobbyWindow">
    1212
    1313        <object style="ModernLabelText" type="text" size="50%-128 0%+4 50%+128 36">