-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(editor): embed Ocean brand mark in Editor UI + setup polish #1231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Scriptwonder
merged 4 commits into
CoplayDev:beta
from
Scriptwonder:feat/editor-logo-setup-polish
Jul 4, 2026
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
e677663
feat(editor): embed Ocean brand mark in Editor UI + setup polish
Scriptwonder 3e36002
fix(editor): polish setup wizard + main-window narrow-width layout
Scriptwonder c46d86d
fix(editor): harden uv-install lifecycle + log brand-icon fallback fa…
Scriptwonder 42d9faa
refactor(editor): drop OceanMark test, tuck version-gated members int…
Scriptwonder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| using System; | ||
| using System.Runtime.InteropServices; | ||
| using MCPForUnity.Editor.Helpers; | ||
|
|
||
| namespace MCPForUnity.Editor.Dependencies | ||
| { | ||
| /// <summary> | ||
| /// Runs the official uv installer for the current platform. UI-agnostic: build the command, | ||
| /// run it (off the main thread), and report the outcome. The caller owns confirmation and | ||
| /// re-checking dependencies afterwards. | ||
| /// Installer reference: https://docs.astral.sh/uv/getting-started/installation/ | ||
| /// </summary> | ||
| public static class UvInstaller | ||
| { | ||
| public readonly struct UvInstallResult | ||
| { | ||
| public readonly bool Success; | ||
| public readonly string Output; | ||
|
|
||
| public UvInstallResult(bool success, string output) | ||
| { | ||
| Success = success; | ||
| Output = output ?? string.Empty; | ||
| } | ||
| } | ||
|
|
||
| /// <summary>One-click install is available on Windows, macOS and Linux.</summary> | ||
| public static bool IsSupported => | ||
| RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || | ||
| RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || | ||
| RuntimeInformation.IsOSPlatform(OSPlatform.Linux); | ||
|
|
||
| /// <summary> | ||
| /// Build the installer invocation for the current platform. Pure and testable — the | ||
| /// returned command is exactly what <see cref="Run"/> executes and what the confirm | ||
| /// dialog shows the user. | ||
| /// </summary> | ||
| public static (string file, string arguments) BuildInstallCommand() | ||
| { | ||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
| { | ||
| return ("powershell", | ||
| "-NoProfile -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\""); | ||
| } | ||
|
|
||
| // macOS and Linux share the POSIX shell installer. | ||
| return ("/bin/sh", "-c \"curl -LsSf https://astral.sh/uv/install.sh | sh\""); | ||
| } | ||
|
|
||
| /// <summary>Human-readable one-line description of the command that will run.</summary> | ||
| public static string DescribeCommand() | ||
| { | ||
| var (file, arguments) = BuildInstallCommand(); | ||
| return $"{file} {arguments}"; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Run the installer and return the outcome. Blocks until the installer exits or the | ||
| /// timeout elapses, so call it off the main thread (e.g. via Task.Run). | ||
| /// </summary> | ||
| public static UvInstallResult Run(int timeoutMs = 180000) | ||
| { | ||
| try | ||
| { | ||
| var (file, arguments) = BuildInstallCommand(); | ||
| bool ok = ExecPath.TryRun(file, arguments, null, out string stdout, out string stderr, timeoutMs); | ||
| return new UvInstallResult(ok, Combine(stdout, stderr)); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| return new UvInstallResult(false, ex.Message); | ||
| } | ||
| } | ||
|
|
||
| private static string Combine(string stdout, string stderr) | ||
| { | ||
| string outText = (stdout ?? string.Empty).Trim(); | ||
| string errText = (stderr ?? string.Empty).Trim(); | ||
| string combined = | ||
| string.IsNullOrEmpty(errText) ? outText : | ||
| string.IsNullOrEmpty(outText) ? errText : | ||
| outText + "\n" + errText; | ||
|
|
||
| // Keep dialogs readable — echo only the tail of long installer output. | ||
| const int max = 1500; | ||
| if (combined.Length > max) | ||
| { | ||
| combined = "…" + combined.Substring(combined.Length - max); | ||
| } | ||
| return combined; | ||
| } | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
163 changes: 163 additions & 0 deletions
163
MCPForUnity/Editor/Windows/Components/Branding/OceanMark.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| using UnityEngine; | ||
| using UnityEngine.UIElements; | ||
|
|
||
| namespace MCPForUnity.Editor.Windows.Components.Branding | ||
| { | ||
| /// <summary> | ||
| /// The MCP for Unity "Ocean split-cube" brand mark. | ||
| /// | ||
| /// On Unity 2022.1+ it is drawn as resolution-independent vector geometry via UI Toolkit's | ||
| /// Painter2D — crisp at any DPI, theme-independent (fixed brand colors matching the source | ||
| /// SVG), and with no package dependency. Painter2D does not exist on the 2021.3 floor, so | ||
| /// there we fall back to the raster brand icon (package-icon.png) that ships with the package. | ||
| /// | ||
| /// Geometry is transcribed from website/static/img/logo-mark.svg (viewBox "30 30 140 140"), | ||
| /// which remains the human-facing source of truth. Size the element via USS (width/height); | ||
| /// the drawing scales to the resolved content box. | ||
| /// </summary> | ||
| public sealed class OceanMark : VisualElement | ||
| { | ||
| public OceanMark() | ||
| { | ||
| #if UNITY_2022_1_OR_NEWER | ||
| generateVisualContent += OnGenerateVisualContent; | ||
| RegisterCallback<GeometryChangedEvent>(_ => MarkDirtyRepaint()); | ||
| #else | ||
| ApplyRasterFallback(); | ||
| #endif | ||
| pickingMode = PickingMode.Ignore; // purely decorative | ||
| } | ||
|
|
||
| #if UNITY_2022_1_OR_NEWER | ||
| // The source SVG viewBox spans 30..170 on both axes (a 140-unit square). | ||
| private const float SvgOrigin = 30f; | ||
| private const float SvgSize = 140f; | ||
|
|
||
| // Brand palette (hex values lifted directly from logo-mark.svg). | ||
| private static readonly Color LeftFill = FromHex(0x2563EB); // blue cube faces | ||
| private static readonly Color LeftStroke = FromHex(0x60A5FA); // blue cube outline | ||
| private static readonly Color RightFill = FromHex(0x0D9488); // teal cube faces | ||
| private static readonly Color RightStroke = FromHex(0x2DD4BF);// teal cube outline | ||
| private static readonly Color Bridge = FromHex(0x22D3EE); // cyan bridge | ||
|
|
||
| private void OnGenerateVisualContent(MeshGenerationContext mgc) | ||
| { | ||
| Rect rect = contentRect; | ||
| float box = Mathf.Min(rect.width, rect.height); | ||
| if (box <= 1f) return; // not laid out yet / too small to draw | ||
|
|
||
| float scale = box / SvgSize; | ||
| Vector2 P(float x, float y) => MapSvgPoint(x, y, rect); | ||
| float S(float w) => w * scale; | ||
|
|
||
| Painter2D p = mgc.painter2D; | ||
| p.lineJoin = LineJoin.Round; | ||
| p.lineCap = LineCap.Round; | ||
|
|
||
| // --- Left cube (blue) --- | ||
| FillPoly(p, WithAlpha(LeftFill, 0.22f), P(94, 40), P(42, 70), P(94, 100)); | ||
| FillPoly(p, WithAlpha(LeftFill, 0.12f), P(42, 70), P(42, 130), P(94, 160), P(94, 100)); | ||
| StrokePath(p, LeftStroke, S(3f), true, P(94, 40), P(42, 70), P(42, 130), P(94, 160)); | ||
| StrokePath(p, WithAlpha(LeftStroke, 0.65f), S(1.8f), false, P(42, 70), P(94, 100)); | ||
|
|
||
| // --- Right cube (teal) --- | ||
| FillPoly(p, WithAlpha(RightFill, 0.22f), P(106, 40), P(158, 70), P(106, 100)); | ||
| FillPoly(p, WithAlpha(RightFill, 0.12f), P(158, 70), P(158, 130), P(106, 160), P(106, 100)); | ||
| StrokePath(p, RightStroke, S(3f), true, P(106, 40), P(158, 70), P(158, 130), P(106, 160)); | ||
| StrokePath(p, WithAlpha(RightStroke, 0.65f), S(1.8f), false, P(158, 70), P(106, 100)); | ||
|
|
||
| // --- Bridge rungs (cyan) --- | ||
| StrokePath(p, Bridge, S(3f), false, P(94, 70), P(106, 70)); | ||
| StrokePath(p, Bridge, S(3f), false, P(94, 100), P(106, 100)); | ||
| StrokePath(p, Bridge, S(3f), false, P(94, 130), P(106, 130)); | ||
|
|
||
| // --- Bridge dots (cyan) --- | ||
| float dot = S(2.2f); | ||
| FillDot(p, Bridge, P(94, 70), dot); | ||
| FillDot(p, Bridge, P(106, 70), dot); | ||
| FillDot(p, Bridge, P(94, 100), dot); | ||
| FillDot(p, Bridge, P(106, 100), dot); | ||
| FillDot(p, Bridge, P(94, 130), dot); | ||
| FillDot(p, Bridge, P(106, 130), dot); | ||
| } | ||
|
|
||
| private static void FillPoly(Painter2D p, Color color, params Vector2[] pts) | ||
| { | ||
| p.fillColor = color; | ||
| p.BeginPath(); | ||
| p.MoveTo(pts[0]); | ||
| for (int i = 1; i < pts.Length; i++) p.LineTo(pts[i]); | ||
| p.ClosePath(); | ||
| p.Fill(); | ||
| } | ||
|
|
||
| private static void StrokePath(Painter2D p, Color color, float width, bool closed, params Vector2[] pts) | ||
| { | ||
| p.strokeColor = color; | ||
| p.lineWidth = width; | ||
| p.BeginPath(); | ||
| p.MoveTo(pts[0]); | ||
| for (int i = 1; i < pts.Length; i++) p.LineTo(pts[i]); | ||
| if (closed) p.ClosePath(); | ||
| p.Stroke(); | ||
| } | ||
|
|
||
| private static void FillDot(Painter2D p, Color color, Vector2 center, float radius) | ||
| { | ||
| if (radius <= 0.01f) return; | ||
| p.fillColor = color; | ||
| p.BeginPath(); | ||
| p.Arc(center, radius, new Angle(0f, AngleUnit.Degree), new Angle(360f, AngleUnit.Degree)); | ||
| p.Fill(); | ||
| } | ||
|
|
||
| private static Color WithAlpha(Color c, float a) | ||
| { | ||
| c.a = a; | ||
| return c; | ||
| } | ||
|
|
||
| /// <summary>Map a point from SVG viewBox space into the element's content rect (square, centered).</summary> | ||
| private static Vector2 MapSvgPoint(float svgX, float svgY, Rect content) | ||
| { | ||
| float box = Mathf.Min(content.width, content.height); | ||
| float scale = box / SvgSize; | ||
| float offsetX = (content.width - box) * 0.5f; | ||
| float offsetY = (content.height - box) * 0.5f; | ||
| return new Vector2( | ||
| offsetX + (svgX - SvgOrigin) * scale, | ||
| offsetY + (svgY - SvgOrigin) * scale); | ||
| } | ||
|
|
||
| /// <summary>Convert a 0xRRGGBB literal into an opaque Color.</summary> | ||
| private static Color FromHex(int rgb) => new Color( | ||
| ((rgb >> 16) & 0xFF) / 255f, | ||
| ((rgb >> 8) & 0xFF) / 255f, | ||
| (rgb & 0xFF) / 255f, | ||
| 1f); | ||
| #else | ||
| // Painter2D is unavailable before Unity 2022.1 — show the raster brand icon instead. | ||
| private void ApplyRasterFallback() | ||
| { | ||
| Texture2D tex = LoadBrandTexture(); | ||
| if (tex == null) return; | ||
| style.backgroundImage = new StyleBackground(tex); | ||
| style.unityBackgroundScaleMode = ScaleMode.ScaleToFit; | ||
| } | ||
|
|
||
| private static Texture2D LoadBrandTexture() | ||
| { | ||
| try | ||
| { | ||
| string root = MCPForUnity.Editor.Helpers.AssetPathUtility.GetMcpPackageRootPath(); | ||
| return UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>($"{root}/package-icon.png"); | ||
| } | ||
| catch (System.Exception ex) | ||
| { | ||
| MCPForUnity.Editor.Helpers.McpLog.Warn($"OceanMark: failed to load brand icon fallback: {ex.Message}"); | ||
| return null; | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| #endif | ||
| } | ||
| } | ||
11 changes: 11 additions & 0 deletions
11
MCPForUnity/Editor/Windows/Components/Branding/OceanMark.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.