using System.Collections.Generic; using UnityEngine; namespace MSCO { /// /// Multiplayer-Launcher im Hauptmenü (dunkles Design mit Seitenleiste, Statusleiste /// und Minimieren). Seiten: P2P (Spieler hostet selbst) und Server Browser (Platzhalter, /// dort kommt später auch Direct Connect zu dedizierten Servern hin). /// public class LobbyUI { enum Page { P2P, ServerBrowser } const float Width = 760f; const float Height = 460f; const float SidebarWidth = 190f; const float StatusHeight = 36f; static readonly Color Background = Hex(0x17, 0x17, 0x1b); static readonly Color Sidebar = Hex(0x0f, 0x0f, 0x12); static readonly Color Field = Hex(0x24, 0x24, 0x2a); static readonly Color FieldFocus = Hex(0x2e, 0x2e, 0x36); static readonly Color Accent = Hex(0xf2, 0x6b, 0x1d); static readonly Color AccentHover = Hex(0xff, 0x82, 0x3a); static readonly Color Secondary = Hex(0x2c, 0x2c, 0x33); static readonly Color SecondaryHover = Hex(0x3a, 0x3a, 0x43); static readonly Color Muted = Hex(0x8e, 0x8e, 0x99); static readonly Color Online = Hex(0x3d, 0xd6, 0x8c); static readonly Color Pending = Hex(0xf2, 0xc2, 0x1d); Page page = Page.P2P; bool minimized; Vector2 playerScroll; Vector2 serverScroll; bool browserLoaded; string selectedServer; // "adresse:port" des geöffneten Server-Profils string searchInput = ""; bool showSteam = true, showNonSteam = true, hideFull; string nameInput; string directInput; const string PrefDirect = "MSCO_LastServer"; bool stylesReady; Texture2D texBackground, texSidebar, texAccent, texOnline, texPending, texMuted, texRow; List shownAll, shownServers; // Stand des letzten Layout-Durchgangs string layoutError; GUIStyle windowBox, sidebarBox, rowBox, accentBox, pillStyle, pillMutedStyle, chipOnStyle, chipOffStyle; GUIStyle logoStyle, taglineStyle, navStyle, navActiveStyle, titleStyle, textStyle, captionStyle, fieldStyle, primaryStyle, secondaryStyle, statusStyle, tabStyle, headerCellStyle, rowNameStyle, rowTagStyle, badgeStyle, toggleStyle, placeholderStyle; /// Fläche, die das Fenster gerade belegt (GUI-Koordinaten, oben = 0). public Rect ScreenRect { get; private set; } /// Fenster offen (nicht minimiert) - dann ist das Spielmenü dahinter gesperrt. public bool IsOpen { get { return !minimized; } } /// Ist die Maus gerade über dem Fenster bzw. der eingeklappten Leiste? public bool ContainsMouse() { Vector3 m = Input.mousePosition; return ScreenRect.Contains(new Vector2(m.x, Screen.height - m.y)); } public void Draw(MultiplayerManager mp) { EnsureStyles(); bool pendingJoin = mp.PendingJoinHost != null && !mp.IsInSession; bool versionWarning = mp.PendingVersionWarning != null && !mp.IsInSession; if (pendingJoin || versionWarning) minimized = false; // JOIN-Link / Warnung: Fenster zeigen if (minimized) { DrawMinimized(mp); return; } var window = new Rect((Screen.width - Width) / 2f, (Screen.height - Height) / 2f, Width, Height); ScreenRect = window; GUI.Box(window, GUIContent.none, windowBox); DrawSidebar(new Rect(window.x, window.y, SidebarWidth, Height - StatusHeight)); var content = new Rect(window.x + SidebarWidth + 32, window.y + 28, Width - SidebarWidth - 64, Height - StatusHeight - 52); GUILayout.BeginArea(content); if (versionWarning) DrawVersionWarning(mp); else if (pendingJoin) DrawJoinConfirm(mp); else if (mp.IsInSession) DrawSession(mp); else if (page == Page.P2P) DrawP2P(mp); else DrawServerBrowser(mp); GUILayout.EndArea(); DrawStatusBar(mp, new Rect(window.x, window.yMax - StatusHeight, Width, StatusHeight)); } // ---------------------------------------------------------------- Bereiche void DrawSidebar(Rect r) { GUI.Box(r, GUIContent.none, sidebarBox); // Logo + Schriftzug Texture2D logo = Branding.LogoTexture; float textX = r.x + 22; if (logo != null) { GUI.DrawTexture(new Rect(r.x + 20, r.y + 20, 44, 44), logo); textX = r.x + 72; } GUI.Label(new Rect(textX, r.y + 24, r.xMax - textX, 40), "MSCO", logoStyle); GUI.Label(new Rect(r.x + 22, r.y + 70, r.width - 30, 20), "MY SUMMER CAR ONLINE", taglineStyle); float y = r.y + 110; if (NavButton(new Rect(r.x, y, r.width, 42), "P2P", page == Page.P2P)) page = Page.P2P; y += 44; if (NavButton(new Rect(r.x, y, r.width, 42), "Server Browser", page == Page.ServerBrowser)) page = Page.ServerBrowser; GUI.Label(new Rect(r.x + 24, r.yMax - 30, r.width - 48, 20), "v" + MSCO.ModVersion, taglineStyle); } bool NavButton(Rect r, string text, bool active) { if (active) GUI.Box(new Rect(r.x + 4, r.y + 8, 4, r.height - 16), GUIContent.none, accentBox); // Akzentbalken links return GUI.Button(r, text, active ? navActiveStyle : navStyle); } void DrawP2P(MultiplayerManager mp) { GUILayout.Label("P2P", titleStyle); GUILayout.Label("Host a game from your own save and invite up to " + (MultiplayerManager.MaxPlayers - 1) + " friends through Steam.", textStyle); GUILayout.Space(18); if (mp.SteamAvailable) { if (GUILayout.Button("HOST GAME", primaryStyle, GUILayout.Height(46), GUILayout.Width(250))) mp.HostSteam(); GUILayout.Space(14); GUILayout.Label("To join a friend: accept their invite in Steam, or right-click them in your " + "Steam friends list and choose \"Join Game\".", textStyle); } else { GUILayout.Label("Steam is not running - start the game through Steam to host or join.", textStyle); } } void DrawServerBrowser(MultiplayerManager mp) { ServerBrowser browser = mp.Browser; // Beim ersten Öffnen laden, danach automatisch alle 15 s (Spielerzahl, Ping, neue Server) if (!browserLoaded || (!browser.Loading && Time.realtimeSinceStartup - browser.LastRefresh > ServerBrowser.AutoRefreshSeconds)) { browserLoaded = true; browser.Refresh(mp); } // Server, die auf die direkte Abfrage nicht antworten (z.B. gerade geschlossen), gar nicht zeigen // Unity-GUI läuft in mehreren Durchgängen (Layout, Klick, Zeichnen). Die Liste nur im Layout-Durchgang // übernehmen - ändert sie sich dazwischen (Aktualisierung im Hintergrund), bricht das Layout sonst ab if (shownAll == null || Event.current.type == EventType.Layout) shownAll = browser.Servers.FindAll(s => !s.Offline); List all = shownAll; // Server-Profil (Klick auf einen Server) if (selectedServer != null) { ServerListEntry selected = all.Find(s => s.Address + ":" + s.Port == selectedServer); DrawServerProfile(mp, selected); return; } GUILayout.BeginHorizontal(); // Titel darf nicht vom REFRESH-Button zusammengedrückt werden (sonst Zeilenumbruch) GUILayout.Label("Server Browser", titleStyle, GUILayout.ExpandWidth(true)); if (GUILayout.Button(browser.Loading ? "LOADING..." : "REFRESH", tabStyle, GUILayout.Height(28), GUILayout.Width(110))) browser.Refresh(mp); GUILayout.EndHorizontal(); // Suche + Filter GUILayout.BeginHorizontal(); searchInput = GUILayout.TextField(searchInput, 40, fieldStyle, GUILayout.Height(26), GUILayout.Width(170)); if (string.IsNullOrEmpty(searchInput)) { Rect field = GUILayoutUtility.GetLastRect(); GUI.Label(new Rect(field.x + 10, field.y, field.width, field.height), "Search...", placeholderStyle); } GUILayout.Space(12); showSteam = Chip(showSteam, "STEAM"); GUILayout.Space(6); showNonSteam = Chip(showNonSteam, "NON-STEAM"); GUILayout.Space(6); hideFull = Chip(hideFull, "HIDE FULL"); GUILayout.EndHorizontal(); GUILayout.Space(6); string search = (searchInput ?? "").Trim().ToLower(); if (shownServers == null || Event.current.type == EventType.Layout) shownServers = all.FindAll(s => (s.RequireSteam ? showSteam : showNonSteam) && (!hideFull || s.Players < s.MaxPlayers) && (search.Length == 0 || (s.Name ?? "").ToLower().Contains(search) || (s.Owner ?? "").ToLower().Contains(search))); List servers = shownServers; string shownError = Event.current.type == EventType.Layout ? (layoutError = browser.Error) : layoutError; // Tabellenkopf Rect header = GUILayoutUtility.GetRect(10, 26, GUILayout.ExpandWidth(true)); GUI.Box(header, GUIContent.none, rowBox); GUI.Label(new Rect(header.x + 12, header.y, header.width * 0.48f, header.height), "SERVER NAME", headerCellStyle); GUI.Label(new Rect(header.x + header.width * 0.5f, header.y, 70, header.height), "PLAYERS", headerCellStyle); GUI.Label(new Rect(header.x + header.width * 0.62f, header.y, 70, header.height), "TYPE", headerCellStyle); GUI.Label(new Rect(header.x + header.width * 0.76f, header.y, 50, header.height), "PING", headerCellStyle); serverScroll = GUILayout.BeginScrollView(serverScroll, GUILayout.Height(140)); if (shownError != null) GUILayout.Label(shownError, textStyle); if (servers.Count == 0) GUILayout.Label(browser.Loading ? "Loading servers..." : all.Count > 0 ? "No servers match your filters." : "No servers online right now.", textStyle); foreach (ServerListEntry s in servers) { Rect row = GUILayoutUtility.GetRect(10, 34, GUILayout.ExpandWidth(true)); GUI.Box(new Rect(row.x, row.y + 2, row.width, row.height - 4), GUIContent.none, rowBox); // Klick auf die Zeile öffnet das Server-Profil (dort wird verbunden) if (GUI.Button(new Rect(row.x, row.y, row.width, row.height), GUIContent.none, GUIStyle.none)) selectedServer = s.Address + ":" + s.Port; GUI.DrawTexture(new Rect(row.x + 10, row.y + 13, 8, 8), s.Ping < 0 ? texPending : texOnline); float nameX = row.x + 26; if (s.IsLocal) { // Server auf diesem PC (localhost) nameX += Pill(nameX, row, "LOCAL", pillStyle); } if (!string.IsNullOrEmpty(s.Version) && s.Version != MSCO.ModVersion) { nameX += Pill(nameX, row, "v" + s.Version, pillStyle); // abweichende Version } GUI.Label(new Rect(nameX, row.y, row.x + row.width * 0.49f - nameX, row.height), s.Name, rowNameStyle); GUI.Label(new Rect(row.x + row.width * 0.5f, row.y, 70, row.height), s.Players + "/" + s.MaxPlayers, rowNameStyle); Pill(row.x + row.width * 0.62f, row, s.RequireSteam ? "STEAM" : "NO STEAM", s.RequireSteam ? pillStyle : pillMutedStyle); GUI.Label(new Rect(row.x + row.width * 0.76f, row.y, 50, row.height), s.Ping < 0 ? "..." : s.Ping + " ms", rowNameStyle); GUI.Label(new Rect(row.xMax - 30, row.y, 20, row.height), "›", headerCellStyle); // Hinweis: Profil öffnen } GUILayout.EndScrollView(); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(); if (!mp.SteamAvailable) { // Ohne Steam: eigener Name (nur für Non-Steam-Server) GUILayout.BeginVertical(GUILayout.Width(150)); GUILayout.Label("YOUR NAME", captionStyle); DrawNameField(mp, 150); GUILayout.EndVertical(); GUILayout.Space(10); } GUILayout.BeginVertical(); GUILayout.Label("DIRECT CONNECT", captionStyle); GUILayout.BeginHorizontal(); directInput = GUILayout.TextField(directInput, fieldStyle, GUILayout.Height(34)); GUILayout.Space(10); if (GUILayout.Button("CONNECT", primaryStyle, GUILayout.Height(34), GUILayout.Width(110))) { string host; int port; if (ServerBrowser.TryParseAddress(directInput, out host, out port)) { PlayerPrefs.SetString(PrefDirect, directInput.Trim()); mp.JoinServer(host, port); } } GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.EndHorizontal(); } static bool CanJoin(MultiplayerManager mp, ServerListEntry s) { return s.Players < s.MaxPlayers && (!s.RequireSteam || mp.SteamAvailable); } void DrawNameField(MultiplayerManager mp, float width) { if (nameInput == null) nameInput = mp.PlayerName; string edited = GUILayout.TextField(nameInput, 24, fieldStyle, GUILayout.Height(34), GUILayout.Width(width)); if (edited != nameInput) { nameInput = edited; if (edited.Trim().Length > 0) mp.PlayerName = edited.Trim(); } } /// Server-Profil: wem der Server gehört, Version, Spieler, Laufzeit ... void DrawServerProfile(MultiplayerManager mp, ServerListEntry s) { if (GUILayout.Button("◂ BACK", tabStyle, GUILayout.Height(26), GUILayout.Width(90))) { selectedServer = null; return; } GUILayout.Space(6); if (s == null) { GUILayout.Label("Server offline", titleStyle); GUILayout.Label("This server does not respond anymore.", textStyle); return; } GUILayout.Label(s.Name, titleStyle); GUILayout.BeginHorizontal(); GUILayout.Label(s.RequireSteam ? "STEAM" : "NON-STEAM", badgeStyle, GUILayout.ExpandWidth(false)); if (s.IsLocal) { GUILayout.Space(10); GUILayout.Label("LOCAL", badgeStyle, GUILayout.ExpandWidth(false)); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(s.Description)) GUILayout.Label(s.Description, textStyle); GUILayout.Space(10); bool versionOk = s.Version == MSCO.ModVersion; GUILayout.BeginHorizontal(); GUILayout.BeginVertical(GUILayout.Width(250)); InfoRow("HOSTED BY", string.IsNullOrEmpty(s.Owner) ? "unknown" : s.Owner, rowNameStyle); InfoRow("VERSION", "v" + s.Version + (versionOk ? "" : " (you: v" + MSCO.ModVersion + ")"), versionOk ? rowNameStyle : badgeStyle); InfoRow("STEAM", s.RequireSteam ? "Required" : "Not required", rowNameStyle); InfoRow("ONLINE FOR", s.UptimeSeconds >= 0 ? FormatUptime(s.UptimeSeconds) : "-", rowNameStyle); GUILayout.EndVertical(); GUILayout.BeginVertical(); InfoRow("PLAYERS", s.Players + " / " + s.MaxPlayers, rowNameStyle); InfoRow("PING", s.Ping < 0 ? "-" : s.Ping + " ms", rowNameStyle); InfoRow("ADDRESS", s.Address + ":" + s.Port, rowNameStyle); GUILayout.EndVertical(); GUILayout.EndHorizontal(); // Spielerliste if (s.PlayerNames != null && s.PlayerNames.Count > 0) { GUILayout.Space(4); GUILayout.Label("ONLINE", captionStyle); GUILayout.Label(string.Join(", ", s.PlayerNames.ToArray()), textStyle); } GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(); GUI.enabled = CanJoin(mp, s); if (GUILayout.Button(s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(42), GUILayout.Width(190))) mp.JoinServer(s.Address, s.Port, s.Name, s.MaxPlayers); GUI.enabled = true; GUILayout.Space(12); if (s.RequireSteam && !mp.SteamAvailable) GUILayout.Label("This server requires Steam.", textStyle); else if (!mp.SteamAvailable) { GUILayout.Label("NAME", captionStyle, GUILayout.Width(42)); DrawNameField(mp, 150); } GUILayout.EndHorizontal(); } /// Kleines abgerundetes Abzeichen, so breit wie sein Text. Gibt die belegte Breite zurück. static float Pill(float x, Rect row, string text, GUIStyle style) { Vector2 size = style.CalcSize(new GUIContent(text)); GUI.Label(new Rect(x, row.y + (row.height - 18) / 2f, size.x, 18), text, style); return size.x + 8; } /// Filter-Chip (an = orange umrandet), ersetzt die Unity-Häkchen. bool Chip(bool on, string text) { if (GUILayout.Button(text, on ? chipOnStyle : chipOffStyle, GUILayout.Height(26), GUILayout.ExpandWidth(false))) on = !on; return on; } void InfoRow(string caption, string value, GUIStyle valueStyle) { GUILayout.Label(caption, captionStyle); GUILayout.Label(value, valueStyle); GUILayout.Space(2); } static string FormatUptime(int seconds) { int days = seconds / 86400, hours = seconds / 3600 % 24, minutes = seconds / 60 % 60; if (days > 0) return days + "d " + hours + "h"; if (hours > 0) return hours + "h " + minutes + "m"; return Mathf.Max(1, minutes) + " min"; } /// Server hat eine andere Version: warnen, Verbinden nur auf Wunsch. void DrawVersionWarning(MultiplayerManager mp) { MultiplayerManager.VersionWarning w = mp.PendingVersionWarning; GUILayout.Label("Version mismatch", titleStyle); GUILayout.Label("This server runs a different MSCO version than you. You can still join, but things may not " + "sync correctly (players, items, cars). For the best experience use the same version.", textStyle); GUILayout.Space(16); GUILayout.Label("SERVER", captionStyle); GUILayout.Label(w.Name + " (" + w.Host + ":" + w.Port + ")", rowNameStyle); GUILayout.Space(8); GUILayout.Label("VERSIONS", captionStyle); GUILayout.Label("Server v" + w.ServerVersion + " - You v" + MSCO.ModVersion, rowTagStyle); GUILayout.Space(22); GUILayout.BeginHorizontal(); if (GUILayout.Button("JOIN ANYWAY", primaryStyle, GUILayout.Height(42), GUILayout.Width(170))) mp.ConfirmVersionWarning(); GUILayout.Space(10); if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(42), GUILayout.Width(170))) mp.CancelVersionWarning(); GUILayout.EndHorizontal(); } /// Nachfrage nach einem JOIN-Link der Website (keine Webseite soll einen ungefragt verbinden). void DrawJoinConfirm(MultiplayerManager mp) { GUILayout.Label("Join server?", titleStyle); GUILayout.Label("You opened a join link from the server list.", textStyle); GUILayout.Space(18); GUILayout.Label("SERVER ADDRESS", captionStyle); GUILayout.Label(mp.PendingJoinHost + ":" + mp.PendingJoinPort, rowNameStyle); GUILayout.Space(24); if (!mp.SteamAvailable) GUILayout.Label("Steam is required to join servers - start the game through Steam.", textStyle); GUILayout.BeginHorizontal(); GUI.enabled = mp.SteamAvailable; if (GUILayout.Button("JOIN", primaryStyle, GUILayout.Height(42), GUILayout.Width(170))) mp.ConfirmPendingJoin(); GUI.enabled = true; GUILayout.Space(10); if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(42), GUILayout.Width(170))) mp.CancelPendingJoin(); GUILayout.EndHorizontal(); } void DrawSession(MultiplayerManager mp) { GUILayout.Label(mp.IsHost ? "Hosting" : "Connected", titleStyle); GUILayout.Label(mp.IsHost ? "Your game is open. Start when everyone has joined." : "Waiting for the host to start the game.", textStyle); GUILayout.Space(16); GUILayout.Label("PLAYERS " + mp.Players.Count + "/" + MultiplayerManager.MaxPlayers, captionStyle); playerScroll = GUILayout.BeginScrollView(playerScroll, GUILayout.Height(170)); foreach (PlayerInfo p in mp.Players) { Rect row = GUILayoutUtility.GetRect(10, 32, GUILayout.ExpandWidth(true)); GUI.Box(new Rect(row.x, row.y, row.width, row.height - 4), GUIContent.none, rowBox); GUI.DrawTexture(new Rect(row.x + 12, row.y + 11, 8, 8), texOnline); GUI.Label(new Rect(row.x + 30, row.y, row.width - 150, row.height - 2), p.Name, rowNameStyle); string tag = p.Id == NetSession.HostId ? "HOST" : ""; if (p.IsLocal) tag = tag.Length > 0 ? tag + " · YOU" : "YOU"; GUI.Label(new Rect(row.xMax - 120, row.y, 110, row.height - 2), tag, rowTagStyle); } GUILayout.EndScrollView(); GUILayout.Space(14); GUILayout.BeginHorizontal(); if (mp.IsHost) { if (GUILayout.Button("START GAME", primaryStyle, GUILayout.Height(42), GUILayout.Width(150))) mp.StartHostGame(); GUILayout.Space(10); if (mp.IsSteamSession && mp.Players.Count < MultiplayerManager.MaxPlayers && GUILayout.Button("INVITE FRIENDS", secondaryStyle, GUILayout.Height(42), GUILayout.Width(150))) mp.InviteFriends(); GUILayout.Space(10); } if (GUILayout.Button("DISCONNECT", secondaryStyle, GUILayout.Height(42), GUILayout.Width(150))) mp.Disconnect(); GUILayout.EndHorizontal(); } void DrawStatusBar(MultiplayerManager mp, Rect r) { GUI.Box(r, GUIContent.none, sidebarBox); Texture2D dot = mp.IsPeerReady ? texOnline : mp.IsInSession ? texPending : texMuted; GUI.DrawTexture(new Rect(r.x + 18, r.y + (r.height - 8) / 2f, 8, 8), dot); GUI.Label(new Rect(r.x + 36, r.y, r.width - 180, r.height), mp.Status, statusStyle); if (GUI.Button(new Rect(r.xMax - 118, r.y + 6, 106, r.height - 12), "MINIMIZE", tabStyle)) minimized = true; } void DrawMinimized(MultiplayerManager mp) { var r = new Rect((Screen.width - 220) / 2f, 0, 220, 30); ScreenRect = r; GUI.Box(r, GUIContent.none, sidebarBox); GUI.Box(new Rect(r.x + 10, r.yMax - 3, r.width - 20, 3), GUIContent.none, accentBox); Texture2D dot = mp.IsPeerReady ? texOnline : mp.IsInSession ? texPending : texMuted; Texture2D logo = Branding.LogoTexture; if (logo != null) GUI.DrawTexture(new Rect(r.x + 8, r.y + 4, 22, 22), logo); GUI.DrawTexture(new Rect(r.x + 36, r.y + 11, 8, 8), dot); if (GUI.Button(new Rect(r.x + 48, r.y, r.width - 48, r.height), "MSCO MULTIPLAYER ▾", tabStyle)) minimized = false; } // ---------------------------------------------------------------- Hilfen void EnsureStyles() { if (stylesReady && texBackground != null) return; // PlayerPrefs erst hier lesen (nicht im Konstruktor erlaubt) directInput = PlayerPrefs.GetString(PrefDirect, ""); texBackground = Solid(Background); texSidebar = Solid(Sidebar); texAccent = Solid(Accent); texOnline = UiTheme.Rounded(Online, 4); // runde Status-Punkte texPending = UiTheme.Rounded(Pending, 4); texMuted = UiTheme.Rounded(Muted, 4); texRow = Solid(Field); windowBox = UiTheme.Panel9(Background, 10); sidebarBox = UiTheme.Panel9(Sidebar, 10); rowBox = UiTheme.Panel9(Field, UiTheme.Radius); accentBox = UiTheme.Panel9(Accent, 1); pillStyle = UiTheme.Panel9(new Color(Accent.r, Accent.g, Accent.b, 0.18f), 4); pillStyle.normal.textColor = Accent; pillStyle.fontSize = 10; pillStyle.fontStyle = FontStyle.Bold; pillStyle.alignment = TextAnchor.MiddleCenter; pillStyle.padding = new RectOffset(7, 7, 0, 0); pillStyle.wordWrap = false; pillMutedStyle = new GUIStyle(pillStyle); pillMutedStyle.normal.background = UiTheme.Rounded(new Color(1f, 1f, 1f, 0.08f), 4); pillMutedStyle.normal.textColor = Muted; chipOnStyle = Button(new Color(Accent.r, Accent.g, Accent.b, 0.22f), new Color(Accent.r, Accent.g, Accent.b, 0.32f), AccentHover, 11, FontStyle.Bold); chipOffStyle = Button(Field, FieldFocus, Muted, 11, FontStyle.Bold); chipOnStyle.padding = chipOffStyle.padding = new RectOffset(12, 12, 0, 0); logoStyle = Label(28, Color.white, FontStyle.Bold); taglineStyle = Label(10, Muted, FontStyle.Bold); titleStyle = Label(24, Color.white, FontStyle.Bold); textStyle = Label(13, Muted, FontStyle.Normal); textStyle.wordWrap = true; captionStyle = Label(11, Muted, FontStyle.Bold); captionStyle.margin.bottom = 6; statusStyle = Label(12, Color.white, FontStyle.Normal); statusStyle.alignment = TextAnchor.MiddleLeft; headerCellStyle = Label(11, Muted, FontStyle.Bold); headerCellStyle.alignment = TextAnchor.MiddleLeft; rowNameStyle = Label(14, Color.white, FontStyle.Normal); rowNameStyle.alignment = TextAnchor.MiddleLeft; rowTagStyle = Label(11, Accent, FontStyle.Bold); rowTagStyle.alignment = TextAnchor.MiddleRight; badgeStyle = new GUIStyle(rowTagStyle); badgeStyle.alignment = TextAnchor.MiddleLeft; badgeStyle.padding = new RectOffset(6, 6, 2, 2); placeholderStyle = Label(13, Muted, FontStyle.Italic); placeholderStyle.alignment = TextAnchor.MiddleLeft; toggleStyle = new GUIStyle(GUI.skin.toggle); toggleStyle.normal.textColor = toggleStyle.onNormal.textColor = toggleStyle.hover.textColor = toggleStyle.onHover.textColor = Color.white; toggleStyle.fontSize = 11; toggleStyle.fontStyle = FontStyle.Bold; navStyle = Button(Sidebar, Secondary, Muted, 14, FontStyle.Normal); navStyle.alignment = TextAnchor.MiddleLeft; navStyle.padding = new RectOffset(26, 10, 0, 0); navActiveStyle = Button(Secondary, Secondary, Color.white, 14, FontStyle.Bold); navActiveStyle.alignment = TextAnchor.MiddleLeft; navActiveStyle.padding = new RectOffset(26, 10, 0, 0); primaryStyle = Button(Accent, AccentHover, Color.white, 14, FontStyle.Bold); secondaryStyle = Button(Secondary, SecondaryHover, Color.white, 14, FontStyle.Bold); tabStyle = Button(Sidebar, Secondary, Muted, 11, FontStyle.Bold); fieldStyle = UiTheme.Field(14); stylesReady = true; } static GUIStyle Label(int size, Color color, FontStyle font) { var s = new GUIStyle(GUI.skin.label); s.fontSize = size; s.fontStyle = font; s.normal.textColor = color; return s; } static GUIStyle Button(Color normal, Color hover, Color text, int size, FontStyle font) { var s = new GUIStyle(GUI.skin.button); // abgerundete Ecken (9-Slice), siehe UiTheme s.normal.background = UiTheme.Rounded(normal); s.hover.background = UiTheme.Rounded(hover); s.active.background = UiTheme.Rounded(hover); s.focused.background = UiTheme.Rounded(normal); s.normal.textColor = s.hover.textColor = s.active.textColor = s.focused.textColor = text; s.border = new RectOffset(UiTheme.Radius + 2, UiTheme.Radius + 2, UiTheme.Radius + 2, UiTheme.Radius + 2); s.fontSize = size; s.fontStyle = font; return s; } static Texture2D Solid(Color color) { var tex = new Texture2D(1, 1); tex.SetPixel(0, 0, color); tex.Apply(); tex.hideFlags = HideFlags.HideAndDontSave; return tex; } static Color Hex(int r, int g, int b) { return new Color(r / 255f, g / 255f, b / 255f, 1f); } } }