diff --git a/OpenDreamClient/Interface/BrowsePopup.cs b/OpenDreamClient/Interface/BrowsePopup.cs index 4102bb17ff..848fd46465 100644 --- a/OpenDreamClient/Interface/BrowsePopup.cs +++ b/OpenDreamClient/Interface/BrowsePopup.cs @@ -17,18 +17,18 @@ internal sealed class BrowsePopup { public BrowsePopup( string name, Vector2i size, - IClydeWindow ownerWindow) { - WindowDescriptor popupWindowDescriptor = new WindowDescriptor(name, - new() { - new ControlDescriptorBrowser { - Id = new DMFPropertyString("browser"), - Size = new DMFPropertySize(size), - Anchor1 = new DMFPropertyPos(0, 0), - Anchor2 = new DMFPropertyPos(100, 100) - } - }) { - Size = new DMFPropertySize(size) - }; + IClydeWindow ownerWindow, + WindowDescriptor? descriptor = null) { + WindowDescriptor popupWindowDescriptor = descriptor ?? new WindowDescriptor(name) { + Size = new DMFPropertySize(size) + }; + + popupWindowDescriptor.ControlDescriptors.Add(new ControlDescriptorBrowser { + Id = new DMFPropertyString("browser"), + Size = new DMFPropertySize(size), + Anchor1 = new DMFPropertyPos(0, 0), + Anchor2 = new DMFPropertyPos(100, 100), + }); WindowElement = new ControlWindow(popupWindowDescriptor); WindowElement.CreateChildControls(); diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index f1b0ab7b90..a7bf0d36f3 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -1,28 +1,29 @@ -using System.IO; -using System.Text; -using System.Globalization; -using OpenDreamShared.Network.Messages; -using OpenDreamClient.Interface.Controls; -using OpenDreamShared.Interface.Descriptors; -using OpenDreamShared.Interface.DMF; +using OpenDreamClient.Interface.Controls; using OpenDreamClient.Interface.Prompts; using OpenDreamClient.Resources; using OpenDreamClient.Resources.ResourceTypes; using OpenDreamShared.Dream; +using OpenDreamShared.Interface.Descriptors; +using OpenDreamShared.Interface.DMF; +using OpenDreamShared.Network.Messages; using Robust.Client; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.ContentPack; +using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Random; using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Timing; using Robust.Shared.Utility; using SixLabors.ImageSharp; +using System.Globalization; +using System.IO; using System.Linq; -using Robust.Shared.Map; +using System.Text; namespace OpenDreamClient.Interface; @@ -203,7 +204,25 @@ private void RxBrowse(MsgBrowse pBrowse) { browser.SetFileSource(null); } } else if (pBrowse.HtmlSource != null) { - var htmlFileName = $"browse_{pBrowse.Window}_{_random.Next()}"; // TODO: Possible collisions and explicit file names + Dictionary? options = null; + + if (pBrowse.Options is not null) { + DMFParser? parser = ParseDmfParams(pBrowse.Options, out var checkParserErrors); + if (parser is not null) { + Dictionary attributes = parser.AttributesValues(); + if (!checkParserErrors()) { + options = attributes; + } + } + } + + string htmlFileName; + if (options?.TryGetValue("file", out var fileName) ?? false) { + htmlFileName = fileName; + } else { + // TODO: Possible collisions + htmlFileName = $"browse_{pBrowse.Window}_{_random.Next()}"; + } ControlBrowser? outputBrowser = referencedElement as ControlBrowser; if (outputBrowser == null) { @@ -219,8 +238,32 @@ private void RxBrowse(MsgBrowse pBrowse) { break; } } else if (pBrowse.Window != null) { + WindowDescriptor? descriptor = null; + (int x, int y) size = (480, 480); + + // Handle using options to set the initial properties of the window + if (options != null) { + var mappingElement = new MappingDataNode { + { "id", pBrowse.Window } + }; + + foreach (var attribute in options) { + mappingElement.Add(attribute.Key, attribute.Value); + } + + descriptor = (WindowDescriptor?)_serializationManager.Read(typeof(WindowDescriptor), mappingElement); + + if (descriptor?.Size.X == 0 || descriptor?.Size.Y == 0) { + descriptor.Size.X = size.x; + descriptor.Size.Y = size.y; + } else if (descriptor != null) { + size.x = descriptor.Size.X; + size.y = descriptor.Size.Y; + } + } + // Creating a new popup - var popup = new BrowsePopup(pBrowse.Window, pBrowse.Size, _clyde.MainWindow); + var popup = new BrowsePopup(pBrowse.Window, size, _clyde.MainWindow, descriptor); popup.Closed += () => { Windows.Remove(pBrowse.Window); }; outputBrowser = popup.Browser; @@ -264,7 +307,7 @@ private void RxWinGet(MsgWinGet message) { MsgPromptResponse response = new() { PromptId = message.PromptId, Type = DreamValueType.Text, - Value = WinGet(message.ControlId, message.QueryValue, forceSnowflake:true) + Value = WinGet(message.ControlId, message.QueryValue, forceSnowflake: true) }; _netManager.ClientSendMessage(response); @@ -366,7 +409,7 @@ public void FrameUpdate(FrameEventArgs frameEventArgs) { } else if (Menus.TryGetValue(windowId, out var menu)) { if (menu.MenuElementsById.TryGetValue(elementId, out var menuElement)) return menuElement; - } else if(MacroSets.TryGetValue(windowId, out var macroSet)) { + } else if (MacroSets.TryGetValue(windowId, out var macroSet)) { if (macroSet.Macros.TryGetValue(elementId, out var macroElement)) return macroElement; } @@ -516,7 +559,7 @@ public void RunCommand(string fullCommand, bool repeating = false) { var result = HandleEmbeddedWinget(null, currentArg.ToString(), out var hadWinget); // 64x64 or 64,64 gets split into two "64 64" args - if (hadWinget && result.Split(['x', ',']) is {Length: 2} wingetSplit && + if (hadWinget && result.Split(['x', ',']) is { Length: 2 } wingetSplit && float.TryParse(wingetSplit[0], out _) && float.TryParse(wingetSplit[1], out _)) { args.Add(wingetSplit[0]); args.Add(wingetSplit[1]); @@ -535,7 +578,7 @@ public void RunCommand(string fullCommand, bool repeating = false) { var result = HandleEmbeddedWinget(null, currentArg.ToString(), out var hadWinget); // 64x64 or 64,64 gets split into two "64 64" args - if (hadWinget && result.Split(['x', ',']) is {Length: 2} wingetSplit && + if (hadWinget && result.Split(['x', ',']) is { Length: 2 } wingetSplit && float.TryParse(wingetSplit[0], out _) && float.TryParse(wingetSplit[1], out _)) { args.Add(wingetSplit[0]); args.Add(wingetSplit[1]); @@ -554,7 +597,7 @@ public void RunCommand(string fullCommand, bool repeating = false) { var result = HandleEmbeddedWinget(null, arg, out var hadWinget); // 64x64 or 64,64 gets split into two "64 64" args - if (hadWinget && result.Split(['x', ',']) is {Length: 2} wingetSplit && + if (hadWinget && result.Split(['x', ',']) is { Length: 2 } wingetSplit && float.TryParse(wingetSplit[0], out _) && float.TryParse(wingetSplit[1], out _)) { args.Add(wingetSplit[0]); args.Add(wingetSplit[1]); @@ -614,35 +657,36 @@ public string HandleEmbeddedWinget(string? controlId, string value, out bool had string result = value; int startPos = result.IndexOf("[[", StringComparison.Ordinal); - while(startPos > -1){ + while (startPos > -1) { int endPos = result.IndexOf("]]", startPos, StringComparison.Ordinal); - if(endPos == -1) + if (endPos == -1) break; - string inner = result.Substring(startPos+2, endPos-startPos-2); + string inner = result.Substring(startPos + 2, endPos - startPos - 2); string[] elementSplit = inner.Split('.'); string innerControlId = controlId ?? ""; - if(elementSplit.Length > 1){ - innerControlId = (string.IsNullOrEmpty(innerControlId) ? "" : innerControlId+".")+string.Join(".", elementSplit[..^1]); + if (elementSplit.Length > 1) { + innerControlId = (string.IsNullOrEmpty(innerControlId) ? "" : innerControlId + ".") + string.Join(".", elementSplit[..^1]); inner = elementSplit[^1]; } string innerResult = WinGet(innerControlId, inner); hadWinget = true; - result = result.Substring(0, startPos) + innerResult + result.Substring(endPos+2); + result = result.Substring(0, startPos) + innerResult + result.Substring(endPos + 2); startPos = result.IndexOf("[[", StringComparison.Ordinal); } return result; } - public void WinSet(string? controlId, string winsetParams) { + private DMFParser? ParseDmfParams(string dmfParams, out Func checkErrors) { + checkErrors = null!; DMFParser parser; - try{ - var lexer = new DMFLexer(winsetParams); + try { + var lexer = new DMFLexer(dmfParams); parser = new DMFParser(lexer, _serializationManager); } catch (Exception e) { _sawmill.Error($"Error parsing winset: {e}"); - return; + return null; } bool CheckParserErrors() { @@ -656,10 +700,21 @@ bool CheckParserErrors() { return true; } + checkErrors = CheckParserErrors; + return parser; + } + + public void WinSet(string? controlId, string winsetParams) { + DMFParser? parser = ParseDmfParams(winsetParams, out var checkParserErrors); + + if (parser == null) { + return; + } + if (string.IsNullOrEmpty(controlId)) { List winSets = parser.GlobalWinSet(); - if (CheckParserErrors()) + if (checkParserErrors()) return; // id=abc overrides the elements of other winsets without an element @@ -678,26 +733,26 @@ bool CheckParserErrors() { _sawmill.Error($"Invalid global winset \"{winsetParams}\""); } } else { - if(winSet.TrueStatements is not null) { + if (winSet.TrueStatements is not null) { InterfaceElement? conditionalElement = FindElementWithId(elementId); - if(conditionalElement is null) + if (conditionalElement is null) _sawmill.Error($"Invalid element on ternary condition \"{elementId}\""); else - if(conditionalElement.TryGetProperty(winSet.Attribute, out var conditionalCheckValue) && conditionalCheckValue.Equals(winSet.Value)) { - foreach(DMFWinSet statement in winSet.TrueStatements) { + if (conditionalElement.TryGetProperty(winSet.Attribute, out var conditionalCheckValue) && conditionalCheckValue.Equals(winSet.Value)) { + foreach (DMFWinSet statement in winSet.TrueStatements) { string statementElementId = statement.Element ?? elementId; InterfaceElement? statementElement = FindElementWithId(statementElementId); - if(statementElement is not null) { + if (statementElement is not null) { statementElement.SetProperty(statement.Attribute, HandleEmbeddedWinget(statementElementId, statement.Value, out _), manualWinset: true); } else { _sawmill.Error($"Invalid element on ternary \"{statementElementId}\""); } } - } else if (winSet.FalseStatements is not null){ - foreach(DMFWinSet statement in winSet.FalseStatements) { + } else if (winSet.FalseStatements is not null) { + foreach (DMFWinSet statement in winSet.FalseStatements) { string statementElementId = statement.Element ?? elementId; InterfaceElement? statementElement = FindElementWithId(statementElementId); - if(statementElement is not null) { + if (statementElement is not null) { statementElement.SetProperty(statement.Attribute, HandleEmbeddedWinget(statementElementId, statement.Value, out _), manualWinset: true); } else { _sawmill.Error($"Invalid element on ternary \"{statementElementId}\""); @@ -719,7 +774,7 @@ bool CheckParserErrors() { InterfaceElement? element = FindElementWithId(controlId); var attributes = parser.AttributesValues(); - if (CheckParserErrors()) + if (checkParserErrors()) return; if (element == null && attributes.TryGetValue("parent", out var parentId)) { @@ -750,16 +805,16 @@ bool ParseAndTryGet(InterfaceElement element, string query, out string result) { //parse "as blah" from query if it's there string[] querySplit = query.Split(" as "); IDMFProperty propResult; - if(querySplit.Length != 2) //must be "thing as blah" or "thing". Anything else is invalid. - if(element.TryGetProperty(query, out propResult!)){ + if (querySplit.Length != 2) //must be "thing as blah" or "thing". Anything else is invalid. + if (element.TryGetProperty(query, out propResult!)) { result = forceJson ? propResult.AsJson() : forceSnowflake ? propResult.AsSnowflake() : propResult.AsRaw(); return true; } else { result = ""; return false; } - else{ - if(!element.TryGetProperty(querySplit[0], out propResult!)) { + else { + if (!element.TryGetProperty(querySplit[0], out propResult!)) { result = ""; return false; } @@ -772,7 +827,7 @@ bool ParseAndTryGet(InterfaceElement element, string query, out string result) { return true; } - switch(querySplit[1]){ + switch (querySplit[1]) { case "arg": result = propResult.AsArg(); break; @@ -812,12 +867,12 @@ string GetProperty(string elementId) { } var multiQuery = queryValue.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - if(multiQuery.Length > 1) { + if (multiQuery.Length > 1) { var result = ""; - foreach(var query in multiQuery) { + foreach (var query in multiQuery) { if (!ParseAndTryGet(element, query, out var queryResult)) _sawmill.Error($"Could not winget property {query} on {element.Id}"); - result += query+"="+queryResult + ";"; + result += query + "=" + queryResult + ";"; } return result.TrimEnd(';'); @@ -930,7 +985,7 @@ public void WinClone(string controlId, string cloneId) { private void Reset() { _uiManager.MainViewport.Visible = false; //close windows if they're open, and clear all child ui elements - foreach (var window in Windows.Values){ + foreach (var window in Windows.Values) { window.CloseChildWindow(); window.UIElement.RemoveAllChildren(); } diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 52256253f9..ad45be51b8 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -1,6 +1,3 @@ -using System.Threading.Tasks; -using System.Web; -using DMCompiler.Bytecode; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Procs.Native; @@ -11,6 +8,8 @@ using Robust.Shared.Enums; using Robust.Shared.Player; using SpaceWizards.Sodium; +using System.Threading.Tasks; +using System.Web; namespace OpenDreamRuntime; @@ -34,7 +33,8 @@ public sealed partial class DreamConnection { [ViewVariables] public DreamObjectClient? Client { get; private set; } [ViewVariables] public string Key { get; } - [ViewVariables] public DreamObjectMob? Mob { + [ViewVariables] + public DreamObjectMob? Mob { get => _mob; set { if (_mob != value) { @@ -66,7 +66,8 @@ [ViewVariables] public DreamObjectMob? Mob { } } - [ViewVariables] public DreamObjectMovable? Eye { + [ViewVariables] + public DreamObjectMovable? Eye { get; set { value?.IncRef(); @@ -448,7 +449,7 @@ public void BrowseResource(DreamResource resource, string filename) { } public void HandleBrowseResourceRequest(string filename) { - if(_permittedBrowseRscFiles.TryGetValue(filename, out var dreamResource)) { + if (_permittedBrowseRscFiles.TryGetValue(filename, out var dreamResource)) { var msg = new MsgBrowseResourceResponse() { Filename = filename, Data = dreamResource.ResourceData!, //honestly if this is null, something mega fucked up has happened and we should error hard @@ -462,7 +463,8 @@ public void HandleBrowseResourceRequest(string filename) { public void Browse(string? body, string? options) { string? window = null; - Vector2i size = (480, 480); + + List unhandledOptions = new List(); if (options != null) { foreach (string option in options.Split(',', ';', '&')) { @@ -475,19 +477,17 @@ public void Browse(string? body, string? options) { if (key == "window") { window = value; - } else if (key == "size") { - string[] sizeSeparated = value.Split("x", 2); - - size = (int.Parse(sizeSeparated[0]), int.Parse(sizeSeparated[1])); + } else { + unhandledOptions.Add(optionTrimmed); } } } } var msg = new MsgBrowse() { - Size = size, Window = window, - HtmlSource = body + HtmlSource = body, + Options = string.Join(';', unhandledOptions) }; Session?.Channel.SendMessage(msg); diff --git a/OpenDreamShared/Network/Messages/MsgBrowse.cs b/OpenDreamShared/Network/Messages/MsgBrowse.cs index cfe606464d..82731b8577 100644 --- a/OpenDreamShared/Network/Messages/MsgBrowse.cs +++ b/OpenDreamShared/Network/Messages/MsgBrowse.cs @@ -1,5 +1,4 @@ using Lidgren.Network; -using Robust.Shared.Maths; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -9,33 +8,34 @@ public sealed class MsgBrowse : NetMessage { public string? Window; public string? HtmlSource; - public Vector2i Size; + public string? Options; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { var hasWindow = buffer.ReadBoolean(); var hasHtml = buffer.ReadBoolean(); + var hasOptions = buffer.ReadBoolean(); buffer.ReadPadBits(); if (hasWindow) Window = buffer.ReadString(); if (hasHtml) HtmlSource = buffer.ReadString(); - - Size = (buffer.ReadUInt16(), buffer.ReadUInt16()); + if (hasOptions) + Options = buffer.ReadString(); } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { buffer.Write(Window != null); buffer.Write(HtmlSource != null); + buffer.Write(Options != null); buffer.WritePadBits(); if (Window != null) buffer.Write(Window); if (HtmlSource != null) buffer.Write(HtmlSource); - - buffer.Write((ushort) Size.X); - buffer.Write((ushort) Size.Y); + if (Options != null) + buffer.Write(Options); } } }