diff --git a/GameData/ConformalDecals/Localization/en-us.cfg b/GameData/ConformalDecals/Localization/en-us.cfg index aa55ce9..022d7ab 100644 --- a/GameData/ConformalDecals/Localization/en-us.cfg +++ b/GameData/ConformalDecals/Localization/en-us.cfg @@ -18,6 +18,14 @@ Localization #LOC_ConformalDecals_gui-aspectratio = Aspect Ratio #LOC_ConformalDecals_gui-select-flag = Select Flag #LOC_ConformalDecals_gui-reset-flag = Reset Flag + #LOC_ConformalDecals_gui-set-text = Set Text + #LOC_ConformalDecals_gui-group-fill = Fill + #LOC_ConformalDecals_gui-group-outline = Outline + #LOC_ConformalDecals_gui-fill = Fill + #LOC_ConformalDecals_gui-set-fill-color = Set Fill Color + #LOC_ConformalDecals_gui-outline = Outline + #LOC_ConformalDecals_gui-outline-width = Outline Width + #LOC_ConformalDecals_gui-set-outline-color = Set Outline Color // PARTS diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index abddca6..b27fa72 100644 Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index b6a10d0..fd8404b 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -89,7 +89,7 @@ - + diff --git a/Source/ConformalDecals/ModuleConformalText.cs b/Source/ConformalDecals/ModuleConformalText.cs index 6e3615a..018e204 100644 --- a/Source/ConformalDecals/ModuleConformalText.cs +++ b/Source/ConformalDecals/ModuleConformalText.cs @@ -1,38 +1,129 @@ using ConformalDecals.Text; using ConformalDecals.UI; +using ConformalDecals.Util; +using TMPro; using UnityEngine; namespace ConformalDecals { - public class ModuleConformalText : ModuleConformalDecal { - [KSPField(isPersistant = true)] public string text = "Hello World!"; + public class ModuleConformalText : ModuleConformalDecal, ISerializationCallbackReceiver { + [KSPField(isPersistant = true)] public string text = "Hello World!"; + [KSPField(isPersistant = true)] public Color fillColor = Color.black; + [KSPField(isPersistant = true)] public Color outlineColor = Color.white; + + // serialization-only fields. do not use except in serialization functions [KSPField(isPersistant = true)] public string fontName = "Calibri SDF"; [KSPField(isPersistant = true)] public int style; [KSPField(isPersistant = true)] public bool vertical; - [KSPField(isPersistant = true)] public Color fillColor = Color.black; - [KSPField(isPersistant = true)] public Color outlineColor = Color.white; - [KSPField(isPersistant = true)] public float outlineWidth; + [KSPField(isPersistant = true)] public float lineSpacing; + [KSPField(isPersistant = true)] public float characterSpacing; + + // KSP TWEAKABLES + + [KSPEvent(guiName = "#LOC_ConformalDecals_gui-set-text", guiActive = false, guiActiveEditor = true)] + public void SetText() { + if (_textEntryController == null) { + _textEntryController = TextEntryController.Create(text, _font, _style, OnTextUpdate); + } + } + + // FILL + + [KSPField(guiName = "#LOC_ConformalDecals_gui-fill", groupName = "decal-fill", groupDisplayName = "#LOC_ConformalDecals_gui-group-fill", + guiActive = false, guiActiveEditor = true, isPersistant = true), + UI_Toggle()] + public bool fillEnabled = true; + + [KSPEvent(guiName = "#LOC_ConformalDecals_gui-fill-color", groupName = "decal-fill", groupDisplayName = "#LOC_ConformalDecals_gui-group-fill", + guiActive = false, guiActiveEditor = true)] + public void SetFillColor() { + if (_fillColorPickerController == null) { + _fillColorPickerController = ColorPickerController.Create(fillColor, OnFillColorUpdate); + } + } + + // OUTLINE + + [KSPField(guiName = "#LOC_ConformalDecals_gui-outline", groupName = "decal-outline", groupDisplayName = "#LOC_ConformalDecals_gui-group-outline", + guiActive = false, guiActiveEditor = true, isPersistant = true), + UI_Toggle()] + public bool outlineEnabled; + + [KSPField(guiName = "#LOC_ConformalDecals_gui-outline-width", groupName = "decal-outline", groupDisplayName = "#LOC_ConformalDecals_gui-group-outline", + guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2"), + UI_FloatRange(stepIncrement = 0.05f)] + public float outlineWidth = 0.1f; + + [KSPEvent(guiName = "#LOC_ConformalDecals_gui-set-outline-color", groupName = "decal-outline", groupDisplayName = "#LOC_ConformalDecals_gui-group-outline", + guiActive = false, guiActiveEditor = true)] + public void SetOutlineColor() { + if (_outlineColorPickerCOntroller == null) { + _outlineColorPickerCOntroller = ColorPickerController.Create(outlineColor, OnOutlineColorUpdate); + } + } private DecalTextStyle _style; - private DecalFont _font; - + private DecalFont _font; + private TextEntryController _textEntryController; private ColorPickerController _fillColorPickerController; private ColorPickerController _outlineColorPickerCOntroller; + public override void OnLoad(ConfigNode node) { + base.OnLoad(node); + OnAfterDeserialize(); + } + + public override void OnSave(ConfigNode node) { + OnBeforeSerialize(); + base.OnSave(node); + } + public override void OnStart(StartState state) { base.OnStart(state); _font = DecalConfig.GetFont(fontName); _style = new DecalTextStyle(); + // handle tweakables + + if (HighLogic.LoadedSceneIsEditor) { + GameEvents.onEditorPartEvent.Add(OnEditorEvent); + } - var decalText = new DecalText("Hello World!", _font, _style); - //TextRenderer.Instance.RenderText(decalText, out var texture, out var window); //materialProperties.AddOrGetTextureProperty("_Decal", true).Texture = texture; UpdateMaterials(); UpdateScale(); } + public override void OnDestroy() { + base.OnDestroy(); + this.Log("OnDestroy"); + + if (HighLogic.LoadedSceneIsEditor) { + GameEvents.onEditorPartEvent.Remove(OnEditorEvent); + } + } + + public void OnPartDeleted() { + this.Log("OnPartDeleted"); + } + + public void OnPartSymmetryDeleted() { + this.Log("OnPartSymmetryDeleted"); + } + + protected new void OnEditorEvent(ConstructionEventType eventType, Part eventPart) { + if (eventPart != part) return; + switch (eventType) { + case ConstructionEventType.PartSymmetryDeleted: + OnPartSymmetryDeleted(); + break; + case ConstructionEventType.PartDeleted: + OnPartDeleted(); + break; + } + } + public void OnTextUpdate(string newText, DecalFont newFont, DecalTextStyle newStyle) { text = newText; _font = newFont; @@ -40,32 +131,40 @@ namespace ConformalDecals { } public void OnFillColorUpdate(Color rgb, Util.ColorHSV hsv) { + fillColor = rgb; Debug.Log($"new fill color: {rgb}, {hsv}"); } public void OnOutlineColorUpdate(Color rgb, Util.ColorHSV hsv) { + outlineColor = rgb; Debug.Log($"new outline color: {rgb}, {hsv}"); } - [KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "#LOC_ConformalDecals_gui-select-flag")] - public void SetText() { - if (_textEntryController == null) { - _textEntryController = TextEntryController.Create(text, _font, _style, OnTextUpdate); + public void OnFillToggle() { + if (!fillEnabled && !outlineEnabled) { + outlineEnabled = true; + OnOutlineToggle(); } } - [KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "Set Fill Color")] - public void SetFillColor() { - if (_fillColorPickerController == null) { - _fillColorPickerController = ColorPickerController.Create(fillColor, OnFillColorUpdate); + public void OnOutlineToggle() { + if (!fillEnabled && !outlineEnabled) { + fillEnabled = true; + OnFillToggle(); } } - [KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "Set Outline Color")] - public void SetOutlineColor() { - if (_outlineColorPickerCOntroller == null) { - _outlineColorPickerCOntroller = ColorPickerController.Create(outlineColor, OnOutlineColorUpdate); - } + public void OnBeforeSerialize() { + fontName = _font.Name; + style = (int) _style.FontStyle; + vertical = _style.Vertical; + lineSpacing = _style.LineSpacing; + characterSpacing = _style.CharacterSpacing; + } + + public void OnAfterDeserialize() { + _font = DecalConfig.GetFont(fontName); + _style = new DecalTextStyle((FontStyles) style, vertical, lineSpacing, characterSpacing); } } } \ No newline at end of file diff --git a/Source/ConformalDecals/Text/TextRenderJob.cs b/Source/ConformalDecals/Text/TextRenderJob.cs index 0d17cbd..b1128ce 100644 --- a/Source/ConformalDecals/Text/TextRenderJob.cs +++ b/Source/ConformalDecals/Text/TextRenderJob.cs @@ -10,19 +10,19 @@ namespace ConformalDecals.Text { public bool IsDone { get; private set; } public readonly TextRenderer.TextRenderEvent onRenderFinished = new TextRenderer.TextRenderEvent(); - - public TextRenderJob(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { + + public TextRenderJob(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { OldText = oldText ?? throw new ArgumentNullException(nameof(oldText)); NewText = newText ?? throw new ArgumentNullException(nameof(newText)); Needed = true; - + if (renderFinishedCallback != null) onRenderFinished.AddListener(renderFinishedCallback); } - - public TextRenderJob( DecalText newText, UnityAction renderFinishedCallback) { + + public TextRenderJob(DecalText newText, UnityAction renderFinishedCallback) { NewText = newText ?? throw new ArgumentNullException(nameof(newText)); Needed = true; - + if (renderFinishedCallback != null) onRenderFinished.AddListener(renderFinishedCallback); } @@ -34,7 +34,7 @@ namespace ConformalDecals.Text { IsStarted = true; } - public void Finish(RenderedText output) { + public void Finish(TextRenderOutput output) { IsDone = true; onRenderFinished.Invoke(output); } diff --git a/Source/ConformalDecals/Text/RenderedText.cs b/Source/ConformalDecals/Text/TextRenderOutput.cs similarity index 73% rename from Source/ConformalDecals/Text/RenderedText.cs rename to Source/ConformalDecals/Text/TextRenderOutput.cs index fc31ae4..f90caa0 100644 --- a/Source/ConformalDecals/Text/RenderedText.cs +++ b/Source/ConformalDecals/Text/TextRenderOutput.cs @@ -1,14 +1,14 @@ using UnityEngine; namespace ConformalDecals.Text { - public class RenderedText { + public class TextRenderOutput { public Texture2D Texture { get; private set; } public Rect Window { get; private set; } public int UserCount { get; set; } - public RenderedText(Texture2D texture, Rect window) { + public TextRenderOutput(Texture2D texture, Rect window) { Texture = texture; Window = window; } diff --git a/Source/ConformalDecals/Text/TextRenderer.cs b/Source/ConformalDecals/Text/TextRenderer.cs index 8cd57df..d07d882 100644 --- a/Source/ConformalDecals/Text/TextRenderer.cs +++ b/Source/ConformalDecals/Text/TextRenderer.cs @@ -21,7 +21,7 @@ namespace ConformalDecals.Text { } [Serializable] - public class TextRenderEvent : UnityEvent { } + public class TextRenderEvent : UnityEvent { } private const string BlitShader = "ConformalDecals/Text Blit"; private const int MaxTextureSize = 4096; @@ -34,26 +34,26 @@ namespace ConformalDecals.Text { private TextMeshPro _tmp; private Material _blitMaterial; - private readonly Dictionary _renderCache = new Dictionary(); - private readonly Queue _renderJobs = new Queue(); + private static readonly Dictionary RenderCache = new Dictionary(); + private static readonly Queue RenderJobs = new Queue(); - public TextRenderJob RenderText(DecalText text, UnityAction renderFinishedCallback) { + public static TextRenderJob RenderText(DecalText text, UnityAction renderFinishedCallback) { var job = new TextRenderJob(text, renderFinishedCallback); - _renderJobs.Enqueue(job); + RenderJobs.Enqueue(job); return job; } - public TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { + public static TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction renderFinishedCallback) { var job = new TextRenderJob(oldText, newText, renderFinishedCallback); - _renderJobs.Enqueue(job); + RenderJobs.Enqueue(job); return job; } - public void UnregisterText(DecalText text) { - if (_renderCache.TryGetValue(text, out var renderedText)) { + public static void UnregisterText(DecalText text) { + if (RenderCache.TryGetValue(text, out var renderedText)) { renderedText.UserCount--; if (renderedText.UserCount <= 0) { - _renderCache.Remove(text); + RenderCache.Remove(text); Destroy(renderedText.Texture); } } @@ -70,13 +70,12 @@ namespace ConformalDecals.Text { } private void Update() { - TextRenderJob nextJob; + bool renderNeeded; do { - if (_renderJobs.Count <= 0) return; - nextJob = _renderJobs.Dequeue(); - } while (nextJob.Needed); - - RunJob(nextJob); + if (RenderJobs.Count <= 0) return; + var nextJob = RenderJobs.Dequeue(); + renderNeeded = nextJob.Needed && RunJob(nextJob); + } while (!renderNeeded); } private void Setup() { @@ -94,47 +93,48 @@ namespace ConformalDecals.Text { _isSetup = true; } - private void RunJob(TextRenderJob job) { - Debug.Log($"Starting Text Rendering Job. queue depth = {_renderJobs.Count}, cache size = {_renderCache.Count}"); + private bool RunJob(TextRenderJob job) { + Debug.Log($"Starting Text Rendering Job. queue depth = {RenderJobs.Count}, cache size = {RenderCache.Count}"); job.Start(); - + Texture2D texture = null; - if (job.OldText != null && _renderCache.TryGetValue(job.OldText, out var oldRender)) { + if (job.OldText != null && RenderCache.TryGetValue(job.OldText, out var oldRender)) { // old output still exists oldRender.UserCount--; - + if (oldRender.UserCount <= 0) { // this is the only usage of this output, so we are free to re-render into the texture Debug.Log("Render output is not shared with other users, so reusing texture and removing cache slot"); - + texture = oldRender.Texture; - _renderCache.Remove(job.OldText); + RenderCache.Remove(job.OldText); } else { // other things are using this render output, so decriment usercount, and we'll make a new entry instead Debug.Log("Render output is shared with other users, so making new output"); } } - + // now that all old references are handled, begin rendering the new output - if (_renderCache.TryGetValue(job.NewText, out var cachedRender)) { + if (RenderCache.TryGetValue(job.NewText, out var cachedRender)) { Debug.Log("Using Cached Render Output"); - Debug.Log($"Finished Text Rendering Job. queue depth = {_renderJobs.Count}, cache size = {_renderCache.Count}"); + Debug.Log($"Finished Text Rendering Job. queue depth = {RenderJobs.Count}, cache size = {RenderCache.Count}"); cachedRender.UserCount++; - return; + return false; } var output = RenderText(job.NewText, texture); - _renderCache.Add(job.NewText, output); - + RenderCache.Add(job.NewText, output); + job.Finish(output); - Debug.Log($"Finished Text Rendering Job. queue depth = {_renderJobs.Count}, cache size = {_renderCache.Count}"); + Debug.Log($"Finished Text Rendering Job. queue depth = {RenderJobs.Count}, cache size = {RenderCache.Count}"); + return true; } - public RenderedText RenderText(DecalText text, Texture2D texture) { + public TextRenderOutput RenderText(DecalText text, Texture2D texture) { // SETUP TMP OBJECT FOR RENDERING _tmp.text = text.FormattedText; _tmp.font = text.Font.FontAsset; @@ -220,7 +220,7 @@ namespace ConformalDecals.Text { // RELEASE RENDERTEX RenderTexture.ReleaseTemporary(renderTex); - return new RenderedText(texture, window); + return new TextRenderOutput(texture, window); } } } \ No newline at end of file