Compare commits

...

43 Commits

Author SHA1 Message Date
Remie Richards
460cf57d7c Fluent Localization (#1584) 2021-02-22 00:36:02 +01:00
Pieter-Jan Briers
a3190a8aca Improvements to SpriteComponent for new angle changes: (#1589)
1. Fixes CopyFrom with new rotation parameters.
2. Adds a couple APIs for ClickableComponent content side.
2021-02-22 00:30:16 +01:00
Pieter-Jan Briers
6921fb2fbf Make UserInterfaceManager not dispose root control on game shutdown.
No real reason to do this and it only risks breaking something.
2021-02-21 23:52:09 +01:00
Fortune117
9954d571de Fix for Crash Caused by the Entity Spawn Menu (#1588) 2021-02-21 20:16:01 +01:00
Pieter-Jan Briers
17fe000a1e Fix math tests due to angle/direction changes. 2021-02-21 20:06:39 +01:00
Pieter-Jan Briers
fba415e765 Bit of work to make Direction.South == Angle.Zero.
This makes Direction no longer follow a cartesian trig circle but oh well. Also helper methods to work with this.

Math tests currently fail, pushing this so Acruid can see about fixing SpriteComponent.
2021-02-21 19:47:42 +01:00
Pieter-Jan Briers
583b7ebf38 WPF layout (#1581) 2021-02-21 12:28:13 +01:00
Acruid
771a256925 Fixes bug with an exception being throw when trying to overwrite a deleted Component. (#1587)
Entity now uses constructor injection instead of property injection.
2021-02-20 23:30:09 -08:00
Acruid
ae79e89347 Added a shared PointLightComponent interface. (#1585)
* Added a shared PointLightComponent interface.

* Fix unit tests.
2021-02-20 16:06:34 -08:00
Acruid
6c7eeb95eb Marks Register and RegisterReference obsolete in IComponentFactory. (#1582) 2021-02-20 12:18:37 -08:00
Acruid
de0bd1887f Sprite Rendering Bugfixes (#1551)
* Added documentation to Clyde on the sprite rendering calls.

* Added a rotation debug entity.

* Non-directional RSIs and raw textures are now rotated properly.

* Directional RSIs and Sprite Smoothing work.

* Remove the Directional flag usages.

* Supports layers with different numbers of directions.

* Fixes window rendering.
2021-02-20 11:06:08 -08:00
metalgearsloth
eb3a815d48 Remove AiLogicProcessor (#1568)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-02-19 21:52:32 -08:00
DrSmugleaf
e2a4dcdff1 Fix comparing by name and not ID for entity prototype updates (#1578) 2021-02-20 02:41:51 +01:00
DrSmugleaf
68b0d7bf2e Fix not clearing the queue after hot reload (#1576) 2021-02-20 01:43:56 +01:00
DrSmugleaf
a9b163992b Fix and add test for PrototypeManager LoadString (#1574) 2021-02-20 01:43:43 +01:00
DrSmugleaf
2409965cf8 Fix build (#1575) 2021-02-20 00:15:43 +01:00
DrSmugleaf
eada37378a Add YAML hot reloading (#1571)
* Implement hot reloading for entity prototypes

* Implement automatic prototype hot-reloading

* Merge fixes

* Add yaml hot reloading and a message to notify the client

* Add reloading only changed files, remove cooldown, add retries and remove IPrototype

* Remove reload command

* Make the client listen for reloads instead and only when focused

* Fix errors

* Only queue a reload when the queue has items in it

* Make fails after 10 retries log instead of throw if reloading

Co-authored-by: Jackson Lewis <inquisitivepenguin@protonmail.com>
2021-02-20 00:02:04 +01:00
DrSmugleaf
0f1da1ba2a Add window focused callback to Clyde (#1573) 2021-02-19 22:10:03 +01:00
Acruid
e0cdcd228e Fixed Timer Namespace in unit tests. 2021-02-18 20:35:34 -08:00
Acruid
fdb5e014b5 PauseManager moved to Shared (#1553)
* Moved IPauseManager from server to shared.

* Moved ITimerManager from Timers to Timing.

* Added missing IConsoleHost to server/client RegisterIoC. Tests work again.
2021-02-18 20:12:26 -08:00
DrSmugleaf
cefcad775b Make addcomp and rmcomp give better feedback and case insensitive (#1570)
* Make addcomp and rmcomp case insensitive

* Fix up names

* Make addcomp and rmcomp give better feedback

* Make addcomp and rmcomp less fail happy
2021-02-18 20:01:14 -08:00
Vera Aguilera Puerto
e40feac1f1 Adds VV autorefresh when right-clicking the refresh button. (#1558)
* Adds VV autorefresh when right-clicking the refresh button.

* cancel token on close

* button tooltip
2021-02-18 00:14:11 -08:00
DrSmugleaf
3ef4ac7452 Make component states dependant on the player getting them (#1569) 2021-02-17 23:48:17 -08:00
Pieter-Jan Briers
93bf1b09e7 Fix disconnecting while connecting causes you to be locked out of the server. 2021-02-17 23:22:11 +01:00
DrSmugleaf
a1e557e870 Add IPrototypeManager method to load a string (#1567) 2021-02-17 13:20:39 -08:00
Pieter-Jan Briers
864adb7445 Add DateTimeStyles to sandbox. 2021-02-17 11:52:36 +01:00
mirrorcult
9e3f3f0c1c vec2i serializer (#1563)
Co-authored-by: cyclowns <cyclowns@protonmail.ch>
2021-02-16 12:19:45 -08:00
DrSmugleaf
a40c4a435c Fix file not found exceptions when starting up the game with a debugger (#1562)
* Fix exceptions when starting up the game

* Remove try catches
2021-02-16 20:05:22 +01:00
DrSmugleaf
17182dd0e8 Engine PR for enabling nullability in Content.Client (#1565) 2021-02-16 20:05:06 +01:00
DrSmugleaf
d8b50044a2 Add (de)serialization for immutable lists (#1549) 2021-02-16 20:04:28 +01:00
Pieter-Jan Briers
4dc396e73d Fixes warning in TypePropertySerialization_Test.cs 2021-02-16 09:20:06 +01:00
Pieter-Jan Briers
6ae0b0e892 Fix [GenerateTypedNameReferences] with sealed types.
Fixes #1546
2021-02-16 09:19:57 +01:00
Pieter-Jan Briers
7162ca3456 Probably fix the bug where people get locked out of the server due to duplicate connction attempts. 2021-02-16 09:02:14 +01:00
Pieter-Jan Briers
1b44c1a1b8 Allow NotImplementedException in sandbox. 2021-02-15 17:57:38 +01:00
Clyybber
5b80b33e00 Change GetFileSystemInfos to EnumerateFileSystemInfos for iteration (#1561) 2021-02-15 16:26:16 +01:00
DrSmugleaf
f05c1b2395 Add Attribute generic constraint to IReflectionManager.FindTypesWithAttribute (#1548) 2021-02-14 02:14:00 +01:00
Pieter-Jan Briers
d9b2c73440 XamlUI improvements.
1. Add XAML namespaces https://spacestation14.io with Avalonia hacks.
2. Make markup extensions work with Avalonia hacks.
3. Add LocExtension for localized strings.
4. Add Vector2 parsing type converter to XAML compilation.
5. Make SS14Window better thanks to these improvements.
2021-02-14 02:09:37 +01:00
Pieter-Jan Briers
29a39f8e0a Adds LocExtension markup extension for XAML.
Does Loc.GetString().
2021-02-13 15:13:05 +01:00
Pieter-Jan Briers
2d72a2bdb5 Server timing improvements.
Add helpers.
Fix desynchronized NetTime.
2021-02-13 11:42:12 +01:00
Pieter-Jan Briers
91da635631 Fix Robust.Shared.Interfaces namespace in ScriptInstanceShared 2021-02-13 11:42:12 +01:00
chairbender
68ab3d504a Various fixes to support new chatbox (#1547)
* #272 no arrow, actually change id on channel changer

* #272 ability to apply style class to child

* #272 try methods for selecting items in OptionButton.cs

* #272 allow escaping right angle bracket in formatted message

* #272 ability to detect when local player is set / unset

* #272 make RemoveModal public since PushModal is as well, so modals can be removed on-demand if needed rather than relying on a click elsewhere

* #272 revert
2021-02-12 18:20:29 -08:00
Pieter-Jan Briers
5187040a64 Remove unsafe code from GrowableStack 2021-02-13 00:47:05 +01:00
DrSmugleaf
e0c63e7ce6 Add SerializedType attribute to specify the id used in !type tags (#1545)
* Add SerializedType attribute to serialize types without the !type tag

* Fix nulls in tests

* Fix null in tests maybe

* Return to type tags

* Fix imports
2021-02-11 13:50:55 -08:00
242 changed files with 4505 additions and 2875 deletions

View File

@@ -574,29 +574,22 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- name: NGetText
- name: Fluent.Net
license: |
The MIT License (MIT)
blushingpenguin and Contributors
Copyright (c) 2012 Vitaly Zilnik
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
http://www.apache.org/licenses/LICENSE-2.0
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
- name: NetSerializer
license: |

View File

@@ -0,0 +1,40 @@
- type: entity
id: debugRotation1
name: dbg_rotation1
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
visible: true
sprite: debugRotation.rsi
state: direction1
placement:
mode: AlignTileAny
- type: entity
id: debugRotation4
name: dbg_rotation4
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
visible: true
sprite: debugRotation.rsi
state: direction4
placement:
mode: AlignTileAny
- type: entity
id: debugRotationTex
name: dbg_rotationTex
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
visible: true
texture: debugRotation.rsi/direction1.png
placement:
mode: AlignTileAny

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

View File

@@ -0,0 +1,17 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"states": [{
"name": "direction1",
"directions": 1,
"delays": [[1.0]]
}, {
"name": "direction4",
"directions": 4,
"delays": [[1.0], [1.0], [1.0], [1.0]]
}
]
}

Binary file not shown.

View File

@@ -0,0 +1,37 @@
using System.Linq;
using Pidgin;
using static Pidgin.Parser;
namespace Robust.Build.Tasks
{
public static class MathParsing
{
public static Parser<char, float> Single { get; } = Real.Select(c => (float) c);
public static Parser<char, float> Single1 { get; }
= Single.Between(SkipWhitespaces);
public static Parser<char, (float, float)> Single2 { get; }
= Single.Before(SkipWhitespaces).Repeat(2).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1]);
});
public static Parser<char, (float, float, float, float)> Single4 { get; }
= Single.Before(SkipWhitespaces).Repeat(4).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1], arr[2], arr[3]);
});
public static Parser<char, float[]> Thickness { get; }
= SkipWhitespaces.Then(
OneOf(
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4})),
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
Try(Single1.Select(c => new[] {c}))
));
}
}

View File

@@ -0,0 +1,32 @@
using System.Reflection.Emit;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
internal class RXamlColorAstNode
: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
{
private readonly IXamlMethod _method;
private readonly string _color;
public RXamlColorAstNode(IXamlLineInfo lineInfo, RXamlWellKnownTypes types, string color) : base(lineInfo)
{
_color = color;
Type = new XamlAstClrTypeReference(lineInfo, types.Color, false);
_method = types.ColorFromXaml;
}
public IXamlAstTypeReference Type { get; }
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(_color);
codeGen.EmitCall(_method);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Linq;
using System.Reflection.Emit;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
public abstract class RXamlVecLikeConstAstNode<T>
: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
where T : unmanaged
{
private readonly IXamlConstructor _constructor;
protected readonly T[] Values;
public RXamlVecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, T[] values)
: base(lineInfo)
{
_constructor = constructor;
Values = values;
var @params = constructor.Parameters;
if (@params.Count != values.Length)
throw new ArgumentException("Invalid amount of parameters");
if (@params.Any(c => c != componentType))
throw new ArgumentException("Invalid constructor: not all parameters match component type");
Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
public IXamlAstTypeReference Type { get; }
public virtual XamlILNodeEmitResult Emit(
XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
codeGen.Newobj(_constructor);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
public sealed class RXamlSingleVecLikeConstAstNode : RXamlVecLikeConstAstNode<float>
{
public RXamlSingleVecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, float[] values)
: base(lineInfo, type, constructor, componentType, values)
{
}
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
foreach (var value in Values)
{
codeGen.Emit(OpCodes.Ldc_R4, value);
}
return base.Emit(context, codeGen);
}
}
public sealed class RXamlInt32VecLikeConstAstNode : RXamlVecLikeConstAstNode<int>
{
public RXamlInt32VecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, int[] values)
: base(lineInfo, type, constructor, componentType, values)
{
}
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
foreach (var value in Values)
{
codeGen.Emit(OpCodes.Ldc_I4, value);
}
return base.Emit(context, codeGen);
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Linq;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
class RXamlWellKnownTypes
{
public XamlTypeWellKnownTypes XamlIlTypes { get; }
public IXamlType Single { get; }
public IXamlType Int32 { get; }
public IXamlType Vector2 { get; }
public IXamlConstructor Vector2ConstructorFull { get; }
public IXamlType Vector2i { get; }
public IXamlConstructor Vector2iConstructorFull { get; }
public IXamlType Thickness { get; }
public IXamlConstructor ThicknessConstructorFull { get; }
public IXamlType Color { get; }
public IXamlMethod ColorFromXaml { get; }
public RXamlWellKnownTypes(TransformerConfiguration cfg)
{
var ts = cfg.TypeSystem;
XamlIlTypes = cfg.WellKnownTypes;
Single = ts.GetType("System.Single");
Int32 = ts.GetType("System.Int32");
(Vector2, Vector2ConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2", Single, 2);
(Vector2i, Vector2iConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2i", Int32, 2);
(Thickness, ThicknessConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Thickness", Single, 4);
(IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount)
{
var type = cfg.TypeSystem.GetType(name);
var ctor = type.GetConstructor(Enumerable.Repeat(componentType, componentCount).ToList());
return (type, ctor);
}
Color = cfg.TypeSystem.GetType("Robust.Shared.Maths.Color");
ColorFromXaml = Color.GetMethod(new FindMethodMethodSignature("FromXaml", Color, XamlIlTypes.String)
{
IsStatic = true
});
}
}
static class RXamlWellKnownTypesExtensions
{
public static RXamlWellKnownTypes GetRobustTypes(this AstTransformationContext ctx)
{
if (ctx.TryGetItem<RXamlWellKnownTypes>(out var rv))
return rv;
ctx.SetItem(rv = new RXamlWellKnownTypes(ctx.Configuration));
return rv;
}
}
}

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="Pidgin" Version="2.5.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,6 +6,7 @@ using Microsoft.Build.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Pidgin;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
@@ -70,7 +71,7 @@ namespace Robust.Build.Tasks
{
XmlnsAttributes =
{
typeSystem.GetType("Robust.Client.UserInterface.XAML.XmlnsDefinitionAttribute"),
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
},
ContentAttributes =
@@ -98,7 +99,7 @@ namespace Robust.Build.Tasks
typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage));
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), CustomValueConverter);
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
@@ -297,6 +298,112 @@ namespace Robust.Build.Tasks
return true;
}
private static bool CustomValueConverter(
AstTransformationContext context,
IXamlAstValueNode node,
IXamlType type,
out IXamlAstValueNode result)
{
if (!(node is XamlAstTextNode textNode))
{
result = null;
return false;
}
var text = textNode.Text;
var types = context.GetRobustTypes();
if (type.Equals(types.Vector2))
{
var foo = MathParsing.Single2.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node);
var (x, y) = foo.Value;
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Vector2, types.Vector2ConstructorFull,
types.Single, new[] {x, y});
return true;
}
if (type.Equals(types.Thickness))
{
var foo = MathParsing.Thickness.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
var val = foo.Value;
float[] full;
if (val.Length == 1)
{
var u = val[0];
full = new[] {u, u, u, u};
}
else if (val.Length == 2)
{
var h = val[0];
var v = val[1];
full = new[] {h, v, h, v};
}
else // 4
{
full = val;
}
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Thickness, types.ThicknessConstructorFull,
types.Single, full);
return true;
}
if (type.Equals(types.Thickness))
{
var foo = MathParsing.Thickness.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
var val = foo.Value;
float[] full;
if (val.Length == 1)
{
var u = val[0];
full = new[] {u, u, u, u};
}
else if (val.Length == 2)
{
var h = val[0];
var v = val[1];
full = new[] {h, v, h, v};
}
else // 4
{
full = val;
}
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Thickness, types.ThicknessConstructorFull,
types.Single, full);
return true;
}
if (type.Equals(types.Color))
{
// TODO: Interpret these colors at XAML compile time instead of at runtime.
result = new RXamlColorAstNode(node, types, text);
return true;
}
result = null;
return false;
}
public const string ContextNameScopeFieldName = "RobustNameScope";
private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder<IXamlILEmitter> typeBuilder, IXamlILEmitter constructor)

View File

@@ -24,6 +24,7 @@ namespace Robust.Client.NameGenerator
{
private const string AttributeName = "Robust.Client.AutoGenerated.GenerateTypedNameReferencesAttribute";
private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
private const string AttributeCode = @"// <auto-generated />
using System;
namespace Robust.Client.AutoGenerated
@@ -54,8 +55,8 @@ namespace Robust.Client.AutoGenerated
{
var clrtype = objectNode.Type.GetClrType();
var isControl = IsControl(clrtype);
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
if (!isControl)
return node;
@@ -80,9 +81,13 @@ namespace Robust.Client.AutoGenerated
return node;
}
public void Push(IXamlAstNode node) { }
public void Push(IXamlAstNode node)
{
}
public void Pop() { }
public void Pop()
{
}
}
private static string GenerateSourceCode(
@@ -97,16 +102,20 @@ namespace Robust.Client.AutoGenerated
var compiler =
new XamlILCompiler(
new TransformerConfiguration(typeSystem, typeSystem.Assemblies[0],
new XamlLanguageTypeMappings(typeSystem)),
new XamlLanguageTypeMappings(typeSystem)
{
XmlnsAttributes = {typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute")}
}),
new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>(), false);
compiler.Transformers.Add(new TypeReferenceResolver());
compiler.Transform(parsed);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var names = NameVisitor.GetNames(initialRoot);
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
var namedControls = names.Select(info => " " +
$"protected global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
$"{fieldAccess} global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
return $@"// <auto-generated />
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -124,7 +133,7 @@ namespace {nameSpace}
public void Execute(GeneratorExecutionContext context)
{
var comp = (CSharpCompilation) context.Compilation;
if(comp.GetTypeByMetadataName(AttributeName) == null)
if (comp.GetTypeByMetadataName(AttributeName) == null)
context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
{
@@ -132,7 +141,7 @@ namespace {nameSpace}
}
var symbols = UnpackAnnotatedTypes(context, comp, receiver);
if(symbols == null)
if (symbols == null)
return;
foreach (var typeSymbol in symbols)
@@ -168,7 +177,8 @@ namespace {nameSpace}
"Usage",
DiagnosticSeverity.Error,
true),
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
Location.Create(xamlFileName, new TextSpan(0, 0),
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
continue;
}
@@ -194,7 +204,8 @@ namespace {nameSpace}
}
}
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context, CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context,
CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
{
var options = (CSharpParseOptions) comp.SyntaxTrees[0].Options;
var compilation =

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Audio.Midi;
using Robust.Client.Console;
using Robust.Client.Debugging;
@@ -10,6 +10,7 @@ using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Client.Prototypes;
using Robust.Client.Reflection;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
@@ -17,6 +18,7 @@ using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -33,7 +35,7 @@ namespace Robust.Client
{
SharedIoC.RegisterIoC();
IoCManager.Register<IPrototypeManager, PrototypeManager>();
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
@@ -59,6 +61,7 @@ namespace Robust.Client
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IFontManager, FontManager>();
IoCManager.Register<IFontManagerInternal, FontManager>();
IoCManager.Register<IMidiManager, MidiManager>();

View File

@@ -481,6 +481,8 @@ namespace Robust.Client.Console.Commands
{
_writeNode(root, 0, writer);
}
shell.WriteLine("Saved guidump");
}
private static void _writeNode(Control control, int indents, TextWriter writer)
@@ -542,7 +544,7 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var window = new SS14Window { CustomMinimumSize = (500, 400)};
var window = new SS14Window { MinSize = (500, 400)};
var tabContainer = new TabContainer();
window.Contents.AddChild(tabContainer);
var scroll = new ScrollContainer();
@@ -562,7 +564,7 @@ namespace Robust.Client.Console.Commands
optionButton.OnItemSelected += eventArgs => optionButton.SelectId(eventArgs.Id);
vBox.AddChild(optionButton);
var tree = new Tree { SizeFlagsVertical = Control.SizeFlags.FillExpand };
var tree = new Tree { VerticalExpand = true };
var root = tree.CreateItem();
root.Text = "Honk!";
var child = tree.CreateItem();
@@ -599,7 +601,7 @@ namespace Robust.Client.Console.Commands
{
grid.AddChild(new Button
{
CustomMinimumSize = (50, 50),
MinSize = (50, 50),
Text = $"{x}, {y}"
});
}
@@ -631,6 +633,29 @@ namespace Robust.Client.Console.Commands
}
});
tabContainer.AddChild(new HSplitContainer
{
Children =
{
new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Red},
Children =
{
new Label{ Text = "FOOBARBAZ"},
}
},
new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Blue},
Children =
{
new Label{ Text = "FOOBARBAZ"},
}
},
}
});
window.OpenCentered();
}
}

View File

@@ -20,8 +20,6 @@ namespace Robust.Client.Console
{
private readonly IReflectionManager _reflectionManager;
protected override Vector2? CustomSize => (300, 300);
private readonly VBoxContainer _watchesVBox;
private readonly LineEdit _addWatchEdit;
private readonly Button _addWatchButton;
@@ -37,12 +35,12 @@ namespace Robust.Client.Console
var mainVBox = new VBoxContainer
{
CustomMinimumSize = (500, 300),
MinSize = (500, 300),
Children =
{
(_watchesVBox = new VBoxContainer
{
SizeFlagsVertical = SizeFlags.FillExpand
VerticalExpand = true
}),
new HBoxContainer
{
@@ -50,7 +48,7 @@ namespace Robust.Client.Console
{
(_addWatchEdit = new HistoryLineEdit
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
}),
(_addWatchButton = new Button
@@ -66,6 +64,8 @@ namespace Robust.Client.Console
_addWatchEdit.OnTextEntered += _ => AddWatch();
Contents.AddChild(mainVBox);
SetSize = (300, 300);
}
private void AddWatch()
@@ -113,7 +113,7 @@ namespace Robust.Client.Console
{
(_outputLabel = new Label
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
ClipText = true
}),
(delButton = new Button
@@ -176,7 +176,7 @@ namespace Robust.Client.Console
{
Text = message,
ClipText = true,
SizeFlagsHorizontal = SizeFlags.FillExpand
HorizontalExpand = true
},
(delButton = new Button {Text = Loc.GetString("Remove")})
}

View File

@@ -26,7 +26,6 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -163,6 +162,7 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
_mapManager.Initialize();

View File

@@ -1,4 +1,4 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
namespace Robust.Client.GameObjects
@@ -25,7 +25,6 @@ namespace Robust.Client.GameObjects
RegisterReference<PhysicsComponent, IPhysBody>();
RegisterReference<PhysicsComponent, IPhysicsComponent>();
RegisterIgnore("KeyBindingInput");
Register<PointLightComponent>();
Register<InputComponent>();

View File

@@ -1,11 +0,0 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedIgnorePauseComponent))]
public sealed class IgnorePauseComponent : SharedIgnorePauseComponent
{
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
@@ -11,8 +11,12 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public class PointLightComponent : Component
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
public class PointLightComponent : Component, IPointLightComponent
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override string Name => "PointLight";
public override uint? NetID => NetIDs.POINT_LIGHT;
@@ -65,6 +69,21 @@ namespace Robust.Client.GameObjects
set => _rotation = value;
}
/// <inheritdoc />
/// <summary>
/// The resource path to the mask texture the light will use.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public string? MaskPath
{
get => _maskPath;
set
{
_maskPath = value;
UpdateMask();
}
}
/// <summary>
/// Set a mask texture that will be applied to the light while rendering.
/// The mask's red channel will be linearly multiplied.p
@@ -117,7 +136,7 @@ namespace Robust.Client.GameObjects
private float _radius = 5;
private bool _visibleNested = true;
private bool _lightOnParent = false;
private bool _lightOnParent;
private Color _color = Color.White;
private Vector2 _offset;
private bool _enabled = true;
@@ -125,6 +144,7 @@ namespace Robust.Client.GameObjects
private Angle _rotation;
private float _energy;
private float _softness;
private string? _maskPath;
/// <summary>
/// Radius, in meters.
@@ -141,6 +161,20 @@ namespace Robust.Client.GameObjects
}
}
private void UpdateMask()
{
if (_maskPath is not null)
Mask = _resourceCache.GetResource<TextureResource>(_maskPath);
else
Mask = null;
}
public override void Initialize()
{
base.Initialize();
UpdateMask();
}
/// <inheritdoc />
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
@@ -180,11 +214,7 @@ namespace Robust.Client.GameObjects
serializer.DataFieldCached(ref _softness, "softness", 1f);
serializer.DataFieldCached(ref _maskAutoRotate, "autoRot", false);
serializer.DataFieldCached(ref _visibleNested, "nestedvisible", true);
if (serializer.Reading && serializer.TryReadDataField<string>("mask", out var value))
{
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(value);
}
serializer.DataFieldCached(ref _maskPath, "mask", null);
}
public override void OnRemove()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Animations;
@@ -49,8 +49,26 @@ namespace Robust.Client.GameObjects
/// Rotation transformations on individual layers still apply.
/// If false, all layers get locked to south and rotation is a transformation.
/// </summary>
[Obsolete("Use NoRotation and/or DirectionOverride")]
bool Directional { get; set; }
/// <summary>
/// All sprite rotation is locked, and will always be drawn upright on
/// the screen, regardless of world or view orientation.
/// </summary>
bool NoRotation {get; set; }
/// <summary>
/// Enables overriding the calculated directional RSI state for this sprite.
/// The state to use is defined in <see cref="DirectionOverride"/>.
/// </summary>
bool EnableDirectionOverride { get; set; }
/// <summary>
/// The directional RSI state that will always be displayed, regardless of orientation.
/// </summary>
Direction DirectionOverride { get; set; }
// NOTE: The below are ALL designed to NOT throw exceptions ever,
// instead making a bunch of noisy error logs.
@@ -64,6 +82,8 @@ namespace Robust.Client.GameObjects
uint RenderOrder { get; set; }
bool IsInert { get; }
Matrix3 GetLocalMatrix();
/// <summary>
/// Sets a layer key to the layer map, creating it if it does not exist.
/// </summary>
@@ -202,5 +222,6 @@ namespace Robust.Client.GameObjects
ISpriteLayer this[object layerKey] { get; }
IEnumerable<ISpriteLayer> AllLayers { get; }
int GetLayerDirectionCount(ISpriteLayer layer);
}
}
}

View File

@@ -101,6 +101,7 @@ namespace Robust.Client.GameObjects
/// If false, all layers get locked to south and rotation is a transformation.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete("Use NoRotation and/or DirectionOverride")]
public bool Directional
{
get => _directional;
@@ -197,6 +198,9 @@ namespace Robust.Client.GameObjects
rotation = other.rotation;
scale = other.scale;
drawDepth = other.drawDepth;
_screenLock = other._screenLock;
_overrideDirection = other._overrideDirection;
_enableOverrideDirection = other._enableOverrideDirection;
Layers = new List<Layer>(other.Layers.Count);
foreach (var otherLayer in other.Layers)
{
@@ -218,6 +222,11 @@ namespace Robust.Client.GameObjects
RenderOrder = other.RenderOrder;
}
public Matrix3 GetLocalMatrix()
{
return Matrix3.CreateTransform(in offset, in rotation, in scale);
}
/// <inheritdoc />
public void LayerMapSet(object key, int layer)
{
@@ -965,40 +974,60 @@ namespace Robust.Client.GameObjects
public ISpriteLayer this[object layerKey] => this[LayerMap[layerKey]];
public IEnumerable<ISpriteLayer> AllLayers => Layers;
internal void Render(DrawingHandleWorld drawingHandle, in Matrix3 worldTransform, Angle worldRotation,
Direction? overrideDirection = null)
// Lobby SpriteView rendering path
internal void Render(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection = null)
{
var angle = Rotation;
if (Directional)
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
}
private bool _screenLock = false;
private Direction _overrideDirection = Direction.South;
private bool _enableOverrideDirection = false;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public bool NoRotation { get => _screenLock; set => _screenLock = value; }
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public Direction DirectionOverride { get => _overrideDirection; set => _overrideDirection = value; }
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public bool EnableDirectionOverride { get => _enableOverrideDirection; set => _enableOverrideDirection = value; }
// Sprite rendering path
internal void Render(DrawingHandleWorld drawingHandle, in Angle worldRotation, in Vector2 worldPosition)
{
Direction? overrideDir = null;
if (_enableOverrideDirection)
{
angle -= worldRotation;
overrideDir = _overrideDirection;
}
RenderInternal(drawingHandle, worldRotation, worldPosition, overrideDir);
}
private void CalcModelMatrix(int numDirs, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
{
Angle angle;
if (_screenLock)
{
angle = Angle.Zero;
}
else
{
angle -= new Angle(MathHelper.PiOver2);
angle = CalcRectWorldAngle(worldRotation, numDirs);
}
var mOffset = Matrix3.CreateTranslation(Offset);
var mRotation = Matrix3.CreateRotation(angle);
Matrix3.Multiply(ref mRotation, ref mOffset, out var transform);
// Only apply scale if needed.
if(!Scale.EqualsApprox(Vector2.One)) transform.Multiply(Matrix3.CreateScale(Scale));
transform.Multiply(worldTransform);
RenderInternal(drawingHandle, worldRotation, overrideDirection, transform);
var sWorldRotation = angle;
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
}
internal void Render(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection = null)
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
{
RenderInternal(drawingHandle, worldRotation, overrideDirection, Matrix3.Identity);
}
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection,
in Matrix3 transform)
{
drawingHandle.SetTransform(transform);
var localMatrix = GetLocalMatrix();
foreach (var layer in Layers)
{
@@ -1007,26 +1036,77 @@ namespace Robust.Client.GameObjects
continue;
}
// TODO: Implement layer-specific rotation and scale.
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
var numDirs = GetLayerDirectionCount(layer);
var texture = GetRenderTexture(layer, worldRotation, overrideDirection);
CalcModelMatrix(numDirs, worldRotation, worldPosition, out var modelMatrix);
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
if (layer.Shader != null)
{
drawingHandle.UseShader(layer.Shader);
}
drawingHandle.DrawTexture(texture, -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter),
color * layer.Color);
if (layer.Shader != null)
{
drawingHandle.UseShader(null);
}
RenderLayer(drawingHandle, layer, worldRotation, overrideDirection);
}
}
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle worldRotation, Direction? overrideDirection)
{
var texture = GetRenderTexture(layer, worldRotation, overrideDirection);
if (layer.Shader != null)
{
drawingHandle.UseShader(layer.Shader);
}
var layerColor = color * layer.Color;
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter);
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
// TODO: Implement layer-specific rotation and scale.
// Apply these directly to the box.
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
// handle.Modulate changes the color
// drawingHandle.SetTransform() is set above, turning the quad into local space vertices
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
if (layer.Shader != null)
{
drawingHandle.UseShader(null);
}
}
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
{
var theta = worldAngle.Theta;
var segSize = (MathF.PI*2) / (numDirections * 2);
var segments = (int)(theta / segSize);
var odd = segments % 2;
var result = theta - (segments * segSize) - (odd * segSize);
return result;
}
public int GetLayerDirectionCount(ISpriteLayer layer)
{
if (!layer.RsiState.IsValid)
return 1;
// Pull texture from RSI state instead.
var rsi = layer.Rsi ?? BaseRSI;
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
{
state = GetFallbackState(resourceCache);
}
return state.Directions switch
{
RSI.State.DirectionType.Dir1 => 1,
RSI.State.DirectionType.Dir4 => 4,
RSI.State.DirectionType.Dir8 => 8,
_ => throw new ArgumentOutOfRangeException()
};
}
private Texture GetRenderTexture(Layer layer, Angle worldRotation, Direction? overrideDirection)
{
var texture = layer.Texture;
@@ -1058,8 +1138,11 @@ namespace Robust.Client.GameObjects
serializer.DataFieldCached(ref drawDepth, "drawdepth", DrawDepthTag.Default,
WithFormat.Constants<DrawDepthTag>());
serializer.DataFieldCached(ref color, "color", Color.White);
serializer.DataFieldCached(ref _directional, "directional", true);
serializer.DataFieldCached(ref _visible, "visible", true);
serializer.DataFieldCached(ref _directional, "directional", true); //TODO: Kill ME
serializer.DataFieldCached(ref _screenLock, "noRot", false);
serializer.DataFieldCached(ref _enableOverrideDirection, "enableOverrideDir", false);
serializer.DataFieldCached(ref _overrideDirection, "overrideDir", Direction.East);
// TODO: Writing?
if (!serializer.Reading)
@@ -1314,7 +1397,6 @@ namespace Robust.Client.GameObjects
Rotation = thestate.Rotation;
Offset = thestate.Offset;
Color = thestate.Color;
Directional = thestate.Directional;
RenderOrder = thestate.RenderOrder;
if (thestate.BaseRsiPath != null && BaseRSI != null)
@@ -1371,16 +1453,28 @@ namespace Robust.Client.GameObjects
}
}
private RSI.State.Direction GetDir(RSI.State.DirectionType type, Angle worldRotation)
private RSI.State.Direction GetDir(RSI.State.DirectionType rsiDirectionType, Angle worldRotation)
{
if (!Directional)
var dir = rsiDirectionType switch
{
return RSI.State.Direction.South;
}
RSI.State.DirectionType.Dir1 => Direction.South,
RSI.State.DirectionType.Dir4 => worldRotation.GetCardinalDir(),
RSI.State.DirectionType.Dir8 => worldRotation.GetDir(),
_ => throw new ArgumentException($"Unknown RSI DirectionType: {rsiDirectionType}.", nameof(rsiDirectionType))
};
var angle = new Angle(worldRotation);
return angle.GetDir().Convert(type);
return dir switch
{
Direction.North => RSI.State.Direction.North,
Direction.South => RSI.State.Direction.South,
Direction.East => RSI.State.Direction.East,
Direction.West => RSI.State.Direction.West,
Direction.SouthEast => RSI.State.Direction.SouthEast,
Direction.SouthWest => RSI.State.Direction.SouthWest,
Direction.NorthEast => RSI.State.Direction.NorthEast,
Direction.NorthWest => RSI.State.Direction.NorthWest,
_ => throw new ArgumentOutOfRangeException(nameof(dir), dir, null)
};
}
private void UpdateIsInert()

View File

@@ -40,7 +40,7 @@ namespace Robust.Client.GameObjects
/// <param name="message">Arguments for this event.</param>
/// <param name="replay">if true, current cmd state will not be checked or updated - use this for "replaying" an
/// old input that was saved or buffered until further processing could be done</param>
public bool HandleInputCommand(ICommonSession session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
{
#if DEBUG

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Shared.GameStates;
@@ -352,7 +353,10 @@ namespace Robust.Client.GameStates
foreach (var component in _componentManager.GetNetComponents(createdEntity))
{
var state = component.GetComponentState();
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
var state = component.GetComponentState(player);
if (state.GetType() == typeof(ComponentState))
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -199,7 +199,9 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
}
entry.sprite.Render(_renderHandle.DrawingHandleWorld, entry.worldMatrix, entry.worldRotation);
var matrix = entry.worldMatrix;
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
entry.sprite.Render(_renderHandle.DrawingHandleWorld, in entry.worldRotation, in worldPosition);
if (entry.sprite.PostShader != null)
{

View File

@@ -775,12 +775,10 @@ namespace Robust.Client.Graphics.Clyde
var worldTransform = transform.WorldMatrix;
var box = occluder.BoundingBox;
// So uh, angle 0 = east... Apparently...
// We account for that here so I don't go insane.
var (tlX, tlY) = worldTransform.Transform(box.BottomLeft);
var (trX, trY) = worldTransform.Transform(box.TopLeft);
var (brX, brY) = worldTransform.Transform(box.TopRight);
var (blX, blY) = worldTransform.Transform(box.BottomRight);
var (tlX, tlY) = worldTransform.Transform(box.TopLeft);
var (trX, trY) = worldTransform.Transform(box.TopRight);
var (brX, brY) = worldTransform.Transform(box.BottomRight);
var (blX, blY) = worldTransform.Transform(box.BottomLeft);
// Faces.
var faceN = new Vector4(tlX, tlY, trX, trY);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Runtime.InteropServices;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
@@ -37,28 +37,55 @@ namespace Robust.Client.Graphics.Clyde
_clyde.DrawSetProjViewTransform(proj, view);
}
/// <summary>
/// Draws a sprite to the screen. The coordinate system is left handed.
/// Make sure to set <see cref="DrawSetModelTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="bl">Bottom left vertex of the quad in object space.</param>
/// <param name="br">Bottom right vertex of the quad in object space.</param>
/// <param name="tl">Top left vertex of the quad in object space.</param>
/// <param name="tr">Top right vertex of the quad in object space.</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
public void DrawTextureScreen(Texture texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr,
in Color modulate, in UIBox2? subRegion)
{
var clydeTexture = ExtractTexture(texture, subRegion, out var csr);
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
var (w, h) = clydeTexture.Size;
var sr = new Box2(csr.Left / w, (h - csr.Top) / h, csr.Right / w, (h - csr.Bottom) / h);
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, modulate, sr);
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
}
/// <summary>
/// Draws a sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawSetModelTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="bl">Bottom left vertex of the quad in object space.</param>
/// <param name="br">Bottom right vertex of the quad in object space.</param>
/// <param name="tl">Top left vertex of the quad in object space.</param>
/// <param name="tr">Top right vertex of the quad in object space.</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
public void DrawTextureWorld(Texture texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr,
Color modulate, in UIBox2? subRegion)
{
var clydeTexture = ExtractTexture(texture, subRegion, out var csr);
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
var (w, h) = clydeTexture.Size;
var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, modulate, sr);
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
}
/// <summary>
/// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas).
/// </summary>
private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
{
if (texture is AtlasTexture atlas)
@@ -383,22 +410,40 @@ namespace Robust.Client.Graphics.Clyde
}
}
public override void DrawTextureRectRegion(Texture texture, Box2 rect, UIBox2? subRegion = null,
Color? modulate = null)
/// <summary>
/// Draws a sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawSetModelTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
public override void DrawTextureRectRegion(Texture texture, Box2 quad,
Color? modulate = null, UIBox2? subRegion = null)
{
var color = (modulate ?? Color.White) * Modulate;
_renderHandle.DrawTextureWorld(texture, rect.BottomLeft, rect.BottomRight,
rect.TopLeft, rect.TopRight, color, subRegion);
_renderHandle.DrawTextureWorld(texture, quad.BottomLeft, quad.BottomRight,
quad.TopLeft, quad.TopRight, color, in subRegion);
}
public override void DrawTextureRectRegion(Texture texture, in Box2Rotated rect,
UIBox2? subRegion = null, Color? modulate = null)
/// <summary>
/// Draws a sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawSetModelTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
public override void DrawTextureRectRegion(Texture texture, in Box2Rotated quad,
Color? modulate = null, UIBox2? subRegion = null)
{
var color = (modulate ?? Color.White) * Modulate;
_renderHandle.DrawTextureWorld(texture, rect.BottomLeft, rect.BottomRight,
rect.TopLeft, rect.TopRight, color, subRegion);
_renderHandle.DrawTextureWorld(texture, quad.BottomLeft, quad.BottomRight,
quad.TopLeft, quad.TopRight, color, in subRegion);
}
public override void DrawPrimitives(DrawPrimitiveTopology primitiveTopology,

View File

@@ -477,10 +477,20 @@ namespace Robust.Client.Graphics.Clyde
_currentMatrixView = view;
}
/// <summary>
/// Draws a texture quad to the screen.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="bl">Bottom left vertex of the quad in object space.</param>
/// <param name="br">Bottom right vertex of the quad in object space.</param>
/// <param name="tl">Top left vertex of the quad in object space.</param>
/// <param name="tr">Top right vertex of the quad in object space.</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
/// <param name="texCoords">The four corners of the texture coordinates, matching the four vertices.</param>
private void DrawTexture(ClydeHandle texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr, in Color modulate,
in Box2 sr)
in Box2 texCoords)
{
EnsureBatchState(texture, modulate, true, GetQuadBatchPrimitiveType(), _queuedShader);
EnsureBatchState(texture, in modulate, true, GetQuadBatchPrimitiveType(), _queuedShader);
bl = _currentMatrixModel.Transform(bl);
br = _currentMatrixModel.Transform(br);
@@ -489,10 +499,10 @@ namespace Robust.Client.Graphics.Clyde
// TODO: split batch if necessary.
var vIdx = BatchVertexIndex;
BatchVertexData[vIdx + 0] = new Vertex2D(bl, sr.BottomLeft);
BatchVertexData[vIdx + 1] = new Vertex2D(br, sr.BottomRight);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, sr.TopRight);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, sr.TopLeft);
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft);
BatchVertexIndex += 4;
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);

View File

@@ -55,6 +55,7 @@ namespace Robust.Client.Graphics.Clyde
private GLFWCallbacks.WindowSizeCallback _windowSizeCallback = default!;
private GLFWCallbacks.WindowContentScaleCallback _windowContentScaleCallback = default!;
private GLFWCallbacks.WindowIconifyCallback _windowIconifyCallback = default!;
private GLFWCallbacks.WindowFocusCallback _windowFocusCallback = default!;
private bool _glfwInitialized;
@@ -62,6 +63,7 @@ namespace Robust.Client.Graphics.Clyde
private Window* _glfwWindow;
private Vector2i _framebufferSize;
private bool _isFocused;
private Vector2i _windowSize;
private Vector2i _prevWindowSize;
private Vector2i _prevWindowPos;
@@ -74,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde
// NOTE: in engine we pretend the framebuffer size is the screen size..
// For practical reasons like UI rendering.
public override Vector2i ScreenSize => _framebufferSize;
public override bool IsFocused => _isFocused;
public Vector2 DefaultWindowScale => _windowScale;
public Vector2 MouseScreenPosition => _lastMousePos;
@@ -231,6 +234,7 @@ namespace Robust.Client.Graphics.Clyde
GLFW.SetMouseButtonCallback(_glfwWindow, _mouseButtonCallback);
GLFW.SetWindowContentScaleCallback(_glfwWindow, _windowContentScaleCallback);
GLFW.SetWindowIconifyCallback(_glfwWindow, _windowIconifyCallback);
GLFW.SetWindowFocusCallback(_glfwWindow, _windowFocusCallback);
GLFW.MakeContextCurrent(_glfwWindow);
@@ -548,6 +552,19 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void OnGlfwWindowFocus(Window* window, bool focused)
{
try
{
_isFocused = focused;
OnWindowFocused?.Invoke(new WindowFocusedEventArgs(focused));
}
catch (Exception e)
{
CatchCallbackException(e);
}
}
private void StoreCallbacks()
{
_errorCallback = OnGlfwError;
@@ -560,6 +577,7 @@ namespace Robust.Client.Graphics.Clyde
_windowSizeCallback = OnGlfwWindowSize;
_windowContentScaleCallback = OnGlfwWindownContentScale;
_windowIconifyCallback = OnGlfwWindowIconify;
_windowFocusCallback = OnGlfwWindowFocus;
}
public override void SetWindowTitle(string title)

View File

@@ -88,7 +88,7 @@ namespace Robust.Client.Graphics.Clyde
public override bool Initialize()
{
base.Initialize();
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
if (!InitWindowing())
@@ -152,6 +152,8 @@ namespace Robust.Client.Graphics.Clyde
public override event Action<WindowResizedEventArgs>? OnWindowResized;
public override event Action<WindowFocusedEventArgs>? OnWindowFocused;
public void Screenshot(ScreenshotType type, Action<Image<Rgb24>> callback)
{
_queuedScreenshots.Add((type, callback));

View File

@@ -21,6 +21,7 @@ namespace Robust.Client.Graphics.Clyde
public IRenderWindow MainWindowRenderTarget { get; }
public override Vector2i ScreenSize { get; } = (1280, 720);
public Vector2 DefaultWindowScale => (1, 1);
public override bool IsFocused => true;
public ShaderInstance InstanceShader(ClydeHandle handle)
{
@@ -79,6 +80,12 @@ namespace Robust.Client.Graphics.Clyde
remove { }
}
public override event Action<WindowFocusedEventArgs> OnWindowFocused
{
add { }
remove { }
}
public void Render()
{
// Nada.

View File

@@ -25,6 +25,8 @@ namespace Robust.Client.Graphics
protected bool VSync { get; private set; } = true;
public abstract Vector2i ScreenSize { get; }
public abstract bool IsFocused { get; }
public abstract void SetWindowTitle(string title);
public virtual bool Initialize()
@@ -45,6 +47,8 @@ namespace Robust.Client.Graphics
public abstract event Action<WindowResizedEventArgs> OnWindowResized;
public abstract event Action<WindowFocusedEventArgs> OnWindowFocused;
protected virtual void ReadConfig()
{
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);

View File

@@ -19,17 +19,18 @@ namespace Robust.Client.Graphics
Disposed = true;
}
public void SetTransform(Vector2 position, Angle rotation, Vector2 scale)
public void SetTransform(in Vector2 position, in Angle rotation, in Vector2 scale)
{
CheckDisposed();
var matrix = Matrix3.Identity;
(matrix.R0C0, matrix.R1C1) = scale;
matrix.Rotate(rotation);
matrix.R0C2 += position.X;
matrix.R1C2 += position.Y;
var matrix = Matrix3.CreateTransform(in position, in rotation, in scale);
SetTransform(in matrix);
}
SetTransform(matrix);
public void SetTransform(in Vector2 position, in Angle rotation)
{
var matrix = Matrix3.CreateTransform(in position, in rotation);
SetTransform(in matrix);
}
public abstract void SetTransform(in Matrix3 matrix);

View File

@@ -6,15 +6,63 @@ namespace Robust.Client.Graphics
{
private const int Ppm = EyeManager.PixelsPerMeter;
/// <summary>
/// Draws an untextured colored rectangle to the world.The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="rect">The four vertices of the quad in object space (or world if the transform is identity.).</param>
/// <param name="color">Color of the rectangle.</param>
/// <param name="filled">Is it filled with color, or just the border lines?</param>
public abstract void DrawRect(Box2 rect, Color color, bool filled = true);
/// <summary>
/// Draws an untextured colored rectangle to the world.The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="rect">The four vertices of the quad in object space (or world if the transform is identity.).
/// The rotation of the rectangle is applied before the transform matrix.</param>
/// <param name="color">Color of the rectangle.</param>
/// <param name="filled">Is it filled with color, or just the border lines?</param>
public abstract void DrawRect(in Box2Rotated rect, Color color, bool filled = true);
public abstract void DrawTextureRectRegion(Texture texture, Box2 rect, UIBox2? subRegion = null,
Color? modulate = null);
/// <summary>
/// Draws a sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
public abstract void DrawTextureRectRegion(Texture texture, Box2 quad,
Color? modulate = null, UIBox2? subRegion = null);
public abstract void DrawTextureRectRegion(Texture texture, in Box2Rotated rect, UIBox2? subRegion = null,
Color? modulate = null);
/// <summary>
/// Draws a sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).
/// The rotation of the rectangle is applied before the transform matrix.</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
public abstract void DrawTextureRectRegion(Texture texture, in Box2Rotated quad,
Color? modulate = null, UIBox2? subRegion = null);
/// <summary>
/// Draws a full texture sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="position">The coordinates of the quad in object space (or world if the transform is identity.).</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
/// <remarks>
/// The sprite will have it's local dimensions calculated so that it has <see cref="EyeManager.PixelsPerMeter"/> texels per meter in the world.
/// </remarks>
public void DrawTexture(Texture texture, Vector2 position, Color? modulate = null)
{
CheckDisposed();
@@ -22,18 +70,35 @@ namespace Robust.Client.Graphics
DrawTextureRect(texture, Box2.FromDimensions(position, texture.Size / (float) Ppm), modulate);
}
public void DrawTextureRect(Texture texture, Box2 rect, Color? modulate = null)
/// <summary>
/// Draws a full texture sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
public void DrawTextureRect(Texture texture, Box2 quad, Color? modulate = null)
{
CheckDisposed();
DrawTextureRectRegion(texture, rect, null, modulate);
DrawTextureRectRegion(texture, quad, modulate);
}
public void DrawTextureRect(Texture texture, in Box2Rotated rect, Color? modulate = null)
/// <summary>
/// Draws a full texture sprite to the world. The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
/// to set the model matrix if needed.
/// </summary>
/// <param name="texture">Texture to draw.</param>
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).
/// The rotation of the rectangle is applied before the transform matrix.</param>
/// <param name="modulate">A color to multiply the texture by when shading.</param>
public void DrawTextureRect(Texture texture, in Box2Rotated quad, Color? modulate = null)
{
CheckDisposed();
DrawTextureRectRegion(texture, rect, null, modulate);
DrawTextureRectRegion(texture, in quad, modulate);
}
}
}

View File

@@ -13,6 +13,8 @@ namespace Robust.Client.Graphics
Vector2i ScreenSize { get; }
bool IsFocused { get; }
/// <summary>
/// The default scale ratio for window contents, given to us by the OS.
/// </summary>
@@ -27,6 +29,8 @@ namespace Robust.Client.Graphics
event Action<WindowResizedEventArgs> OnWindowResized;
event Action<WindowFocusedEventArgs> OnWindowFocused;
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null);

View File

@@ -12,7 +12,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
public sealed class ShaderPrototype : IPrototype, IIndexedPrototype
public sealed class ShaderPrototype : IPrototype
{
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;

View File

@@ -0,0 +1,14 @@
using System;
namespace Robust.Client.Graphics
{
public class WindowFocusedEventArgs : EventArgs
{
public WindowFocusedEventArgs(bool focused)
{
Focused = focused;
}
public bool Focused { get; }
}
}

View File

@@ -12,6 +12,11 @@ namespace Robust.Client.Player
LocalPlayer? LocalPlayer { get; }
/// <summary>
/// Invoked after LocalPlayer is changed
/// </summary>
event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
int PlayerCount { get; }
int MaxPlayers { get; }
event EventHandler PlayerListUpdated;
@@ -23,4 +28,15 @@ namespace Robust.Client.Player
void ApplyPlayerStates(IEnumerable<PlayerState>? list);
}
public class LocalPlayerChangedEventArgs : EventArgs
{
public readonly LocalPlayer? OldPlayer;
public readonly LocalPlayer? NewPlayer;
public LocalPlayerChangedEventArgs(LocalPlayer? oldPlayer, LocalPlayer? newPlayer)
{
OldPlayer = oldPlayer;
NewPlayer = newPlayer;
}
}
}

View File

@@ -37,7 +37,21 @@ namespace Robust.Client.Player
public int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? 0;
/// <inheritdoc />
[ViewVariables] public LocalPlayer? LocalPlayer { get; private set; }
[ViewVariables]
public LocalPlayer? LocalPlayer
{
get => _localPlayer;
private set
{
if (_localPlayer == value) return;
var oldValue = _localPlayer;
_localPlayer = value;
LocalPlayerChanged?.Invoke(new LocalPlayerChangedEventArgs(oldValue, _localPlayer));
}
}
private LocalPlayer? _localPlayer;
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
/// <inheritdoc />
[ViewVariables] public IEnumerable<IPlayerSession> Sessions => _sessions.Values;

View File

@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.Prototypes
{
public sealed class ClientPrototypeManager : PrototypeManager
{
[Dependency] private readonly IClyde _clyde = default!;
private readonly List<FileSystemWatcher> _watchers = new();
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();
private readonly HashSet<ResourcePath> _reloadQueue = new();
public override void Initialize()
{
base.Initialize();
NetManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, accept: NetMessageAccept.Server);
_clyde.OnWindowFocused += WindowFocusedChanged;
WatchResources();
}
private void WindowFocusedChanged(WindowFocusedEventArgs args)
{
#if !FULL_RELEASE
if (args.Focused && _reloadQueue.Count > 0)
{
Timer.Spawn(_reloadDelay, ReloadPrototypeQueue, _reloadToken.Token);
}
else
{
_reloadToken.Cancel();
_reloadToken = new CancellationTokenSource();
}
#endif
}
private void ReloadPrototypeQueue()
{
#if !FULL_RELEASE
var then = DateTime.Now;
var msg = NetManager.CreateNetMessage<MsgReloadPrototypes>();
msg.Paths = _reloadQueue.ToArray();
NetManager.ClientSendMessage(msg);
foreach (var path in _reloadQueue)
{
ReloadPrototypes(path);
}
_reloadQueue.Clear();
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
#endif
}
private void WatchResources()
{
#if !FULL_RELEASE
foreach (var path in Resources.GetContentRoots().Select(r => r.ToString())
.Where(r => Directory.Exists(r + "/Prototypes")).Select(p => p + "/Prototypes"))
{
var watcher = new FileSystemWatcher(path, "*.yml")
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += (_, args) =>
{
switch (args.ChangeType)
{
case WatcherChangeTypes.Renamed:
case WatcherChangeTypes.Deleted:
return;
case WatcherChangeTypes.Created:
// case WatcherChangeTypes.Deleted:
case WatcherChangeTypes.Changed:
case WatcherChangeTypes.All:
break;
default:
throw new ArgumentOutOfRangeException();
}
TaskManager.RunOnMainThread(() =>
{
var file = new ResourcePath(args.FullPath);
foreach (var root in IoCManager.Resolve<IResourceManager>().GetContentRoots())
{
if (!file.TryRelativeTo(root, out var relative))
{
continue;
}
_reloadQueue.Add(relative);
}
});
};
watcher.EnableRaisingEvents = true;
_watchers.Add(watcher);
}
#endif
}
}
}

View File

@@ -1,30 +1,54 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface
{
// Code and design heavily inspired by WPF/Avalonia.
public partial class Control
{
public event Action<Control>? OnMinimumSizeChanged;
private Vector2 _size;
[ViewVariables] internal Vector2? PreviousMeasure;
[ViewVariables] internal UIBox2? PreviousArrange;
private float _sizeFlagsStretchRatio = 1;
private Vector2? _calculatedMinimumSize;
private Vector2 _customMinimumSize;
private SizeFlags _sizeFlagsHorizontal = SizeFlags.Fill;
private SizeFlags _sizeFlagsVertical = SizeFlags.Fill;
private bool _layoutDirty;
private float _minWidth;
private float _minHeight;
private float _setWidth = float.NaN;
private float _setHeight = float.NaN;
private float _maxWidth = float.PositiveInfinity;
private float _maxHeight = float.PositiveInfinity;
private bool _horizontalExpand;
private bool _verticalExpand;
private HAlignment _horizontalAlignment;
private VAlignment _verticalAlignment;
private Thickness _margin;
private bool _isLayoutUpdateOverrideUsed;
private bool _measuring;
[ViewVariables] public Vector2 DesiredSize { get; private set; }
[ViewVariables] public Vector2i DesiredPixelSize => (Vector2i) (DesiredSize * UIScale);
[ViewVariables] public bool IsMeasureValid { get; private set; }
[ViewVariables] public bool IsArrangeValid { get; private set; }
[ViewVariables]
public Thickness Margin
{
get => _margin;
set => _margin = value;
}
/// <summary>
/// Called when the <see cref="UIScale"/> for this control changes.
/// </summary>
protected internal virtual void UIScaleChanged()
{
MinimumSizeChanged();
InvalidateMeasure();
}
/// <summary>
@@ -56,7 +80,6 @@ namespace Robust.Client.UserInterface
_size = value;
Resized();
UpdateLayout();
}
}
@@ -179,12 +202,36 @@ namespace Robust.Client.UserInterface
/// Horizontal size flags for container layout.
/// </summary>
[ViewVariables]
[Obsolete("Use HorizontalAlignment and HorizontalExpand instead.")]
public SizeFlags SizeFlagsHorizontal
{
get => _sizeFlagsHorizontal;
get
{
var flags = HorizontalAlignment switch
{
HAlignment.Stretch => SizeFlags.Fill,
HAlignment.Left => SizeFlags.None,
HAlignment.Center => SizeFlags.ShrinkCenter,
HAlignment.Right => SizeFlags.ShrinkEnd,
_ => throw new ArgumentOutOfRangeException()
};
if (_horizontalExpand)
flags |= SizeFlags.Expand;
return flags;
}
set
{
_sizeFlagsHorizontal = value;
HorizontalExpand = (value & SizeFlags.Expand) != 0;
HorizontalAlignment = (value & ~SizeFlags.Expand) switch
{
SizeFlags.None => HAlignment.Left,
SizeFlags.Fill => HAlignment.Stretch,
SizeFlags.ShrinkCenter => HAlignment.Center,
SizeFlags.ShrinkEnd => HAlignment.Right,
_ => throw new ArgumentOutOfRangeException()
};
Parent?.UpdateLayout();
}
@@ -193,18 +240,87 @@ namespace Robust.Client.UserInterface
/// <summary>
/// Vertical size flags for container layout.
/// </summary>
[Obsolete("Use VerticalAlignment and VerticalExpand instead.")]
[ViewVariables]
public SizeFlags SizeFlagsVertical
{
get => _sizeFlagsVertical;
get
{
var flags = _verticalAlignment switch
{
VAlignment.Stretch => SizeFlags.Fill,
VAlignment.Top => SizeFlags.None,
VAlignment.Center => SizeFlags.ShrinkCenter,
VAlignment.Bottom => SizeFlags.ShrinkEnd,
_ => throw new ArgumentOutOfRangeException()
};
if (_verticalExpand)
flags |= SizeFlags.Expand;
return flags;
}
set
{
_sizeFlagsVertical = value;
VerticalExpand = (value & SizeFlags.Expand) != 0;
VerticalAlignment = (value & ~SizeFlags.Expand) switch
{
SizeFlags.None => VAlignment.Top,
SizeFlags.Fill => VAlignment.Stretch,
SizeFlags.ShrinkCenter => VAlignment.Center,
SizeFlags.ShrinkEnd => VAlignment.Bottom,
_ => throw new ArgumentOutOfRangeException()
};
Parent?.UpdateLayout();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public HAlignment HorizontalAlignment
{
get => _horizontalAlignment;
set
{
_horizontalAlignment = value;
InvalidateArrange();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public VAlignment VerticalAlignment
{
get => _verticalAlignment;
set
{
_verticalAlignment = value;
InvalidateArrange();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool HorizontalExpand
{
get => _horizontalExpand;
set
{
_horizontalExpand = value;
Parent?.InvalidateMeasure();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool VerticalExpand
{
get => _verticalExpand;
set
{
_verticalExpand = value;
Parent?.InvalidateArrange();
}
}
/// <summary>
/// Stretch ratio used to give shared of the available space in case multiple siblings are set to expand
/// in a container
@@ -225,12 +341,12 @@ namespace Robust.Client.UserInterface
_sizeFlagsStretchRatio = value;
Parent?.UpdateLayout();
Parent?.InvalidateArrange();
}
}
/// <summary>
/// A combination of <see cref="CustomMinimumSize" /> and <see cref="CalculateMinimumSize" />,
/// A combination of <see cref="MinSize" /> and <see cref="CalculateMinimumSize" />,
/// Whichever is greater.
/// Use this for whenever you need the *actual* minimum size of something.
/// </summary>
@@ -238,24 +354,13 @@ namespace Robust.Client.UserInterface
/// This is in virtual pixels.
/// </remarks>
/// <seealso cref="CombinedPixelMinimumSize"/>
[ViewVariables]
public Vector2 CombinedMinimumSize
{
get
{
if (!_calculatedMinimumSize.HasValue)
{
_updateMinimumSize();
DebugTools.Assert(_calculatedMinimumSize.HasValue);
}
return Vector2.ComponentMax(CustomMinimumSize, _calculatedMinimumSize!.Value);
}
}
[Obsolete("Use DesiredSize and Measure()")]
public Vector2 CombinedMinimumSize => DesiredSize;
/// <summary>
/// The <see cref="CombinedMinimumSize"/>, in physical pixels.
/// </summary>
[Obsolete("Use DesiredSize and Measure()")]
public Vector2i CombinedPixelMinimumSize => (Vector2i) (CombinedMinimumSize * UIScale);
/// <summary>
@@ -264,24 +369,95 @@ namespace Robust.Client.UserInterface
/// <seealso cref="CalculateMinimumSize" />
/// <seealso cref="CombinedMinimumSize" />
[ViewVariables]
[Obsolete("Use MinSize instead.")]
public Vector2 CustomMinimumSize
{
get => _customMinimumSize;
get => (_minWidth, _minHeight);
set => (MinWidth, MinHeight) = Vector2.ComponentMax(Vector2.Zero, value);
}
public Vector2 MinSize
{
get => (_minWidth, _minHeight);
set => (MinWidth, MinHeight) = Vector2.ComponentMax(Vector2.Zero, value);
}
public Vector2 SetSize
{
get => (_setWidth, _setHeight);
set => (SetWidth, SetHeight) = value;
}
public Vector2 MaxSize
{
get => (_maxWidth, _maxHeight);
set => (MaxWidth, MaxHeight) = value;
}
[ViewVariables(VVAccess.ReadWrite)]
public float MinWidth
{
get => _minWidth;
set
{
_customMinimumSize = Vector2.ComponentMax(Vector2.Zero, value);
MinimumSizeChanged();
_minWidth = value;
InvalidateMeasure();
}
}
private void _updateMinimumSize()
[ViewVariables(VVAccess.ReadWrite)]
public float MinHeight
{
if (_stylingDirty)
get => _minHeight;
set
{
ForceRunStyleUpdate();
_minHeight = value;
InvalidateMeasure();
}
}
_calculatedMinimumSize = Vector2.ComponentMax(Vector2.Zero, CalculateMinimumSize());
[ViewVariables(VVAccess.ReadWrite)]
public float SetWidth
{
get => _setWidth;
set
{
_setWidth = value;
InvalidateMeasure();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public float SetHeight
{
get => _setHeight;
set
{
_setHeight = value;
InvalidateMeasure();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public float MaxWidth
{
get => _maxWidth;
set
{
_maxWidth = value;
InvalidateMeasure();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public float MaxHeight
{
get => _maxHeight;
set
{
_maxHeight = value;
InvalidateMeasure();
}
}
/// <summary>
@@ -289,27 +465,31 @@ namespace Robust.Client.UserInterface
/// Do NOT call this directly to get the minimum size for layout purposes!
/// Use <see cref="CombinedMinimumSize" /> for the ACTUAL minimum size.
/// </summary>
[Obsolete("Implement MeasureOverride instead")]
protected virtual Vector2 CalculateMinimumSize()
{
var min = Vector2.Zero;
foreach (var child in Children)
{
min = Vector2.ComponentMax(min, child.CombinedMinimumSize);
}
return min;
return Vector2.Zero;
}
/// <summary>
/// Tells the GUI system that the minimum size of this control may have changed,
/// so that say containers will re-sort it if necessary.
/// </summary>
[Obsolete("Use InvalidateMeasure()")]
public void MinimumSizeChanged()
{
_calculatedMinimumSize = null;
OnMinimumSizeChanged?.Invoke(this);
InvalidateMeasure();
}
Parent?.MinimumSizeChanged();
UpdateLayout();
public void InvalidateMeasure()
{
if (!IsMeasureValid)
return;
IsMeasureValid = false;
IsArrangeValid = false;
UserInterfaceManagerInternal.QueueMeasureUpdate(this);
}
/// <summary>
@@ -320,89 +500,241 @@ namespace Robust.Client.UserInterface
/// where running the deferred layout updating system in the UI manager can be annoying.
/// If you are forced to use this in regular code, you have found a bug.
/// </remarks>
[Obsolete("Call Arrange manually for unit tests or call Measure manually for early measures.")]
public void ForceRunLayoutUpdate()
{
DoLayoutUpdate();
foreach (var child in Children)
{
child.ForceRunLayoutUpdate();
}
// TODO: Fix or remove this
if (PreviousArrange.HasValue)
Arrange(PreviousArrange.Value);
}
protected void UpdateLayout()
public void InvalidateArrange()
{
if (_layoutDirty)
if (!IsArrangeValid)
{
// Already queued for a layout update, don't bother.
return;
}
_layoutDirty = true;
UserInterfaceManagerInternal.QueueLayoutUpdate(this);
IsArrangeValid = false;
UserInterfaceManagerInternal.QueueArrangeUpdate(this);
}
protected void FitChildInPixelBox(Control child, UIBox2i pixelBox)
[Obsolete("Use InvalidateArrange()")]
protected void UpdateLayout()
{
var topLeft = pixelBox.TopLeft / UIScale;
var bottomRight = pixelBox.BottomRight / UIScale;
FitChildInBox(child, new UIBox2(topLeft, bottomRight));
InvalidateArrange();
}
protected void FitChildInBox(Control child, UIBox2 box)
public void Measure(Vector2 availableSize)
{
DebugTools.Assert(child.Parent == this);
if (!IsMeasureValid || PreviousMeasure != availableSize)
{
IsMeasureValid = true;
var desired = MeasureCore(availableSize);
var (minX, minY) = child.CombinedMinimumSize;
var newPosX = box.Left;
var newSizeX = minX;
if (desired.X < 0 || desired.Y < 0 || !float.IsFinite(desired.X) || !float.IsFinite(desired.Y))
throw new InvalidOperationException("Invalid size returned from Measure()");
if ((child.SizeFlagsHorizontal & SizeFlags.ShrinkEnd) != 0)
{
newPosX += (box.Width - minX);
}
else if ((child.SizeFlagsHorizontal & SizeFlags.ShrinkCenter) != 0)
{
newPosX += (box.Width - minX) / 2;
}
else if ((child.SizeFlagsHorizontal & SizeFlags.Fill) != 0)
{
newSizeX = Math.Max(box.Width, newSizeX);
}
var prev = DesiredSize;
DesiredSize = desired;
PreviousMeasure = availableSize;
var newPosY = box.Top;
var newSizeY = minY;
if ((child.SizeFlagsVertical & SizeFlags.ShrinkEnd) != 0)
{
newPosY += (box.Height - minY);
if (prev != desired && Parent != null && !Parent._measuring)
Parent?.InvalidateMeasure();
}
else if ((child.SizeFlagsVertical & SizeFlags.ShrinkCenter) != 0)
{
newPosY += (box.Height - minY) / 2;
}
else if ((child.SizeFlagsVertical & SizeFlags.Fill) != 0)
{
newSizeY = Math.Max(box.Height, newSizeY);
}
child.Position = new Vector2(newPosX, newPosY);
child.Size = new Vector2(newSizeX, newSizeY);
}
internal void DoLayoutUpdate()
protected virtual Vector2 MeasureCore(Vector2 availableSize)
{
if (!Visible)
return default;
if (_stylingDirty)
ForceRunStyleUpdate();
var withoutMargin = _margin.Deflate(availableSize);
var constrained = ApplySizeConstraints(this, withoutMargin);
Vector2 measured;
try
{
_measuring = true;
measured = Vector2.ComponentMax(
MeasureOverride(constrained),
// For the time being keep the old CalculateMinimumSize around.
#pragma warning disable 618
CalculateMinimumSize());
#pragma warning restore 618
}
finally
{
_measuring = false;
}
if (!float.IsNaN(SetWidth))
{
measured.X = SetWidth;
}
measured.X = Math.Clamp(measured.X, MinWidth, MaxWidth);
if (!float.IsNaN(SetHeight))
{
measured.Y = SetHeight;
}
measured.Y = Math.Clamp(measured.Y, MinHeight, MaxHeight);
measured = _margin.Inflate(measured);
return Vector2.ComponentMin(measured, availableSize);
}
protected virtual Vector2 MeasureOverride(Vector2 availableSize)
{
var min = Vector2.Zero;
foreach (var child in Children)
{
child.Measure(availableSize);
min = Vector2.ComponentMax(min, child.DesiredSize);
}
return min;
}
public void ArrangePixel(UIBox2i finalRect)
{
var topLeft = finalRect.TopLeft / UIScale;
var bottomRight = finalRect.BottomRight / UIScale;
Arrange(new UIBox2(topLeft, bottomRight));
}
public void Arrange(UIBox2 finalRect)
{
if (!IsMeasureValid)
Measure(PreviousMeasure ?? finalRect.Size);
if (!IsArrangeValid || PreviousArrange != finalRect)
{
IsArrangeValid = true;
ArrangeCore(finalRect);
PreviousArrange = finalRect;
}
}
protected virtual void ArrangeCore(UIBox2 finalRect)
{
if (!Visible)
return;
var withoutMargins = _margin.Deflate(finalRect);
var availWithoutMargins = withoutMargins.Size;
var size = availWithoutMargins;
var origin = withoutMargins.TopLeft;
if (_horizontalAlignment != HAlignment.Stretch)
size.X = Math.Min(size.X, DesiredSize.X - _margin.SumHorizontal);
if (_verticalAlignment != VAlignment.Stretch)
size.Y = Math.Min(size.Y, DesiredSize.Y - _margin.SumVertical);
size = ApplySizeConstraints(this, size);
Size = size;
_isLayoutUpdateOverrideUsed = true;
#pragma warning disable 618
LayoutUpdateOverride();
_layoutDirty = false;
#pragma warning restore 618
if (!_isLayoutUpdateOverrideUsed)
{
var arranged = ArrangeOverride(size);
size = Vector2.ComponentMin(arranged, size);
}
switch (HorizontalAlignment)
{
case HAlignment.Stretch:
case HAlignment.Center:
origin.X += (availWithoutMargins.X - size.X) / 2;
break;
case HAlignment.Right:
origin.X += availWithoutMargins.X - size.X;
break;
}
switch (VerticalAlignment)
{
case VAlignment.Stretch:
case VAlignment.Center:
origin.Y += (availWithoutMargins.Y - size.Y) / 2;
break;
case VAlignment.Bottom:
origin.Y += availWithoutMargins.Y - size.Y;
break;
}
Position = origin;
Size = size;
}
protected virtual void LayoutUpdateOverride()
protected virtual Vector2 ArrangeOverride(Vector2 finalSize)
{
foreach (var child in Children)
{
FitChildInPixelBox(child, PixelSizeBox);
child.Arrange(UIBox2.FromDimensions(Vector2.Zero, finalSize));
}
return finalSize;
}
[Obsolete("Use Control.ArrangePixel")]
protected void FitChildInPixelBox(Control child, UIBox2i pixelBox)
{
child.ArrangePixel(pixelBox);
}
[Obsolete("Use Control.Arrange")]
protected void FitChildInBox(Control child, UIBox2 box)
{
child.Arrange(box);
}
[Obsolete("Implement ArrangeOverride instead.")]
protected virtual void LayoutUpdateOverride()
{
_isLayoutUpdateOverrideUsed = false;
}
private static Vector2 ApplySizeConstraints(Control control, Vector2 avail)
{
var minW = control._minWidth;
var setW = control._setWidth;
var maxW = control._maxWidth;
var maxConstraint = float.IsNaN(setW) ? float.PositiveInfinity : setW;
maxW = MathHelper.Clamp(maxConstraint, minW, maxW);
var minConstraint = float.IsNaN(setW) ? 0 : setW;
minW = MathHelper.Clamp(maxW, minConstraint, minW);
var minH = control._minHeight;
var setH = control._setHeight;
var maxH = control._maxHeight;
maxConstraint = float.IsNaN(setH) ? float.PositiveInfinity : setH;
maxH = MathHelper.Clamp(maxConstraint, minH, maxH);
minConstraint = float.IsNaN(setH) ? 0 : setH;
minH = MathHelper.Clamp(minW, minConstraint, minH);
return (
Math.Clamp(avail.X, minW, maxW),
Math.Clamp(avail.Y, minH, maxH));
}
/// <summary>
@@ -443,5 +775,21 @@ namespace Robust.Client.UserInterface
/// </summary>
ShrinkEnd = 8,
}
public enum HAlignment
{
Stretch,
Left,
Center,
Right
}
public enum VAlignment
{
Stretch,
Top,
Center,
Bottom
}
}
}

View File

@@ -233,7 +233,7 @@ namespace Robust.Client.UserInterface
protected virtual void StylePropertiesChanged()
{
MinimumSizeChanged();
InvalidateMeasure();
}
public void ForceRunStyleUpdate()

View File

@@ -154,7 +154,8 @@ namespace Robust.Client.UserInterface
_propagateVisibilityChanged(value);
// TODO: unhardcode this.
// Many containers ignore children if they're invisible, so that's why we're replicating that ehre.
Parent?.MinimumSizeChanged();
Parent?.InvalidateMeasure();
InvalidateMeasure();
}
}
@@ -594,7 +595,7 @@ namespace Robust.Client.UserInterface
/// <param name="newChild">The new child.</param>
protected virtual void ChildAdded(Control newChild)
{
MinimumSizeChanged();
InvalidateMeasure();
}
/// <summary>
@@ -604,7 +605,7 @@ namespace Robust.Client.UserInterface
protected virtual void Parented(Control newParent)
{
StylesheetUpdateRecursive();
UpdateLayout();
InvalidateMeasure();
}
/// <summary>
@@ -642,7 +643,7 @@ namespace Robust.Client.UserInterface
/// <param name="child">The former child.</param>
protected virtual void ChildRemoved(Control child)
{
MinimumSizeChanged();
InvalidateMeasure();
}
/// <summary>

View File

@@ -35,8 +35,60 @@ namespace Robust.Client.UserInterface.Controls
public int? SeparationOverride { get; set; }
protected override void LayoutUpdateOverride()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var separation = ActualSeparation;
var minSize = Vector2.Zero;
var first = true;
foreach (var child in Children)
{
if (!child.Visible)
{
continue;
}
child.Measure(availableSize);
var childSize = child.DesiredSize;
if (Vertical)
{
var taken = childSize.Y;
if (!first)
{
taken += separation;
}
minSize.Y += taken;
availableSize.Y = Math.Max(0, availableSize.Y - taken);
first = false;
minSize.X = Math.Max(minSize.X, childSize.X);
}
else
{
var taken = childSize.X;
if (!first)
{
taken += separation;
}
minSize.X += taken;
availableSize.X = Math.Max(0, availableSize.X - taken);
first = false;
minSize.Y = Math.Max(minSize.Y, childSize.Y);
}
}
return minSize;
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var finalPixel = (Vector2i) (finalSize * UIScale);
var separation = (int) (ActualSeparation * UIScale);
// Step one: figure out the sizes of all our children and whether they want to stretch.
@@ -51,19 +103,20 @@ namespace Robust.Client.UserInterface.Controls
{
continue;
}
var (minX, minY) = child.CombinedPixelMinimumSize;
var (minX, minY) = child.DesiredPixelSize;
int minSize;
bool stretch;
if (Vertical)
{
minSize = minY;
stretch = (child.SizeFlagsVertical & SizeFlags.Expand) == SizeFlags.Expand;
stretch = child.VerticalExpand;
}
else
{
minSize = minX;
stretch = (child.SizeFlagsHorizontal & SizeFlags.Expand) == SizeFlags.Expand;
stretch = child.HorizontalExpand;
}
if (!stretch)
@@ -78,7 +131,7 @@ namespace Robust.Client.UserInterface.Controls
sizeList.Add((child, minSize, minSize, stretch));
}
var stretchMax = Vertical ? PixelHeight : PixelWidth;
var stretchMax = Vertical ? finalPixel.Y : finalPixel.X;
stretchMax -= separation * (ChildCount - 1);
// This is the amount of space allocated for stretchable children.
@@ -152,62 +205,19 @@ namespace Robust.Client.UserInterface.Controls
UIBox2i targetBox;
if (Vertical)
{
targetBox = new UIBox2i(0, offset, PixelWidth, offset+size);
targetBox = new UIBox2i(0, offset, finalPixel.X, offset + size);
}
else
{
targetBox = new UIBox2i(offset, 0, offset+size, PixelHeight);
targetBox = new UIBox2i(offset, 0, offset + size, finalPixel.Y);
}
FitChildInPixelBox(control, targetBox);
control.ArrangePixel(targetBox);
offset += size;
}
}
protected override Vector2 CalculateMinimumSize()
{
var separation = ActualSeparation;
var minWidth = 0f;
var minHeight = 0f;
var first = true;
foreach (var child in Children)
{
if (!child.Visible)
{
continue;
}
var (childWidth, childHeight) = child.CombinedMinimumSize;
if (Vertical)
{
minHeight += childHeight;
if (!first)
{
minHeight += separation;
}
first = false;
minWidth = MathF.Max(minWidth, childWidth);
}
else
{
minWidth += childWidth;
if (!first)
{
minWidth += separation;
}
first = false;
minHeight = MathF.Max(minHeight, childHeight);
}
}
return new Vector2(minWidth, minHeight);
return finalSize;
}
public enum AlignMode : byte

View File

@@ -7,15 +7,19 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public class CenterContainer : Container
{
protected override void LayoutUpdateOverride()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var max = Vector2.Zero;
foreach (var child in Children)
{
var childSize = child.CombinedMinimumSize;
var childPos = (Size - childSize) / 2;
var childSize = child.DesiredSize;
var childPos = (finalSize - childSize) / 2;
FitChildInBox(child, UIBox2.FromDimensions(childPos, childSize));
child.Arrange(UIBox2.FromDimensions(childPos, childSize));
max = Vector2.ComponentMax(max, childSize);
}
return max;
}
}
}

View File

@@ -31,13 +31,30 @@ namespace Robust.Client.UserInterface.Controls
}
}
protected override void LayoutUpdateOverride()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var contentBox = ActualStyleBox.GetContentBox(PixelSizeBox);
var boxSize = ActualStyleBox.MinimumSize / UIScale;
var childBox = Vector2.ComponentMax(availableSize - boxSize, Vector2.Zero);
var min = Vector2.Zero;
foreach (var child in Children)
{
FitChildInPixelBox(child, (UIBox2i) contentBox);
child.Measure(childBox);
min = Vector2.ComponentMax(min, child.DesiredSize);
}
return min + boxSize;
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var contentBox = ActualStyleBox.GetContentBox(UIBox2.FromDimensions(Vector2.Zero, finalSize * UIScale));
foreach (var child in Children)
{
child.ArrangePixel((UIBox2i) contentBox);
}
return finalSize;
}
protected internal override void Draw(DrawingHandleScreen handle)
@@ -49,17 +66,6 @@ namespace Robust.Client.UserInterface.Controls
style.Draw(handle, drawBox);
}
protected override Vector2 CalculateMinimumSize()
{
var min = Vector2.Zero;
foreach (var child in Children)
{
min = Vector2.ComponentMax(min, child.CombinedMinimumSize);
}
return min + ActualStyleBox.MinimumSize / UIScale;
}
protected override void DrawModeChanged()
{
switch (DrawMode)

View File

@@ -49,8 +49,8 @@ namespace Robust.Client.UserInterface.Controls
_lineEdit = new LineEdit
{
CustomMinimumSize = new Vector2(40, 0),
SizeFlagsHorizontal = SizeFlags.FillExpand
MinSize = new Vector2(40, 0),
HorizontalExpand = true,
};
AddChild(_lineEdit);

View File

@@ -48,7 +48,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_expandBackwards = value;
UpdateLayout();
InvalidateArrange();
}
}
private bool _expandBackwards;
@@ -100,10 +100,17 @@ namespace Robust.Client.UserInterface.Controls
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the value assigned is less than or equal to 0.
/// </exception>
public float MaxWidth
[Obsolete("Use MaxGridWidth")]
public new float MaxWidth
{
set => MaxGridWidth = value;
}
public float MaxGridWidth
{
set => SetMaxSize(Dimension.Column, value);
}
/// <summary>
/// The max height (in virtual pixels) the grid of elements can have. This dynamically determines
/// the number of rows based on the size of the elements. Setting this puts this grid
@@ -120,12 +127,17 @@ namespace Robust.Client.UserInterface.Controls
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the value assigned is less than or equal to 0.
/// </exception>
public float MaxHeight
[Obsolete("Use MaxGridHeight")]
public new float MaxHeight
{
set => MaxGridHeight = value;
}
public float MaxGridHeight
{
set => SetMaxSize(Dimension.Row, value);
}
private int? _vSeparationOverride;
[SuppressMessage("ReSharper", "StringLiteralTypo")]
@@ -199,8 +211,7 @@ namespace Robust.Client.UserInterface.Controls
_limitType = LimitType.Count;
_limitedDimensionCount = value;
MinimumSizeChanged();
UpdateLayout();
InvalidateMeasure();
}
private void SetMaxSize(Dimension forDimension, float value)
@@ -214,8 +225,7 @@ namespace Robust.Client.UserInterface.Controls
_limitType = LimitType.Size;
_limitSize = value;
MinimumSizeChanged();
UpdateLayout();
InvalidateMeasure();
}
/// <summary>
@@ -263,7 +273,7 @@ namespace Robust.Client.UserInterface.Controls
int maxMinHeight = -1;
foreach (var child in Children)
{
var (minSizeX, minSizeY) = child.CombinedPixelMinimumSize;
var (minSizeX, minSizeY) = child.DesiredPixelSize;
maxMinWidth = Math.Max(maxMinWidth, minSizeX);
maxMinHeight = Math.Max(maxMinHeight, minSizeY);
}
@@ -271,7 +281,7 @@ namespace Robust.Client.UserInterface.Controls
return new Vector2i(maxMinWidth, maxMinHeight);
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
// to make it easier to read and visualize, we're just going to use the terms "x" and "y", width, and height,
// rows and cols,
@@ -280,6 +290,12 @@ namespace Robust.Client.UserInterface.Controls
// For the below convention, we pretend that columns have a limit defined, thus
// the amount of rows is not limited (unlimited).
foreach (var child in Children)
{
// TODO: This is not really correct in any fucking way but I CBA to fix this properly.
child.Measure(availableSize);
}
var rows = GetCount(UnlimitedDimension);
var cols = GetCount(LimitedDimension);
var cellSize = CellSize();
@@ -304,7 +320,7 @@ namespace Robust.Client.UserInterface.Controls
// also converting here to our "pretend" scenario where columns have a limit defined.
// note if we are limiting by size rather than count, the size of each child is constant (cell size)
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.CombinedPixelMinimumSize : cellSize;
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.DesiredPixelSize : cellSize;
var minSizeX = _limitDimension == Dimension.Column ? minSizeXActual : minSizeYActual;
var minSizeY = _limitDimension == Dimension.Column ? minSizeYActual : minSizeXActual;
minColWidth[column] = Math.Max(minSizeX, minColWidth[column]);
@@ -348,9 +364,9 @@ namespace Robust.Client.UserInterface.Controls
return totalSize;
}
protected override void LayoutUpdateOverride()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
// to make it easier to read and visualize, we're just going to use the terms "x" and "y", width, and height,
// to make it easier to read and visualize, we're just going to use the terms "x" and "y", width, and height,
// rows and cols,
// but at the start of the method here we'll set those to what they actually are based
// on the limited dimension, which might involve swapping them.
@@ -390,19 +406,19 @@ namespace Robust.Client.UserInterface.Controls
// converting here to our "pretend" scenario where columns have a limit defined
// note if we are limiting by size rather than count, the size of each child is constant (cell size)
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.CombinedPixelMinimumSize : cellSize;
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.DesiredPixelSize : cellSize;
var minSizeX = _limitDimension == Dimension.Column ? minSizeXActual : minSizeYActual;
var minSizeY = _limitDimension == Dimension.Column ? minSizeYActual : minSizeXActual;
minColWidth[column] = Math.Max(minSizeX, minColWidth[column]);
minRowHeight[row] = Math.Max(minSizeY, minRowHeight[row]);
var colSizeFlag = _limitDimension == Dimension.Column
? child.SizeFlagsHorizontal
: child.SizeFlagsVertical;
var rowSizeFlag = UnlimitedDimension == Dimension.Column
? child.SizeFlagsHorizontal
: child.SizeFlagsVertical;
colExpand[column] = colExpand[column] || (colSizeFlag & SizeFlags.Expand) != 0;
rowExpand[row] = rowExpand[row] || (rowSizeFlag & SizeFlags.Expand) != 0;
var colExpandFlag = _limitDimension == Dimension.Column
? child.HorizontalExpand
: child.VerticalExpand;
var rowExpandFlag = UnlimitedDimension == Dimension.Column
? child.HorizontalExpand
: child.VerticalExpand;
colExpand[column] = colExpand[column] || colExpandFlag;
rowExpand[row] = rowExpand[row] || rowExpandFlag;
index += 1;
}
@@ -550,10 +566,12 @@ namespace Robust.Client.UserInterface.Controls
var boxHeight = _limitDimension == Dimension.Column ? minRowHeight[row] : minColWidth[column];
var box = UIBox2i.FromDimensions(left, top, boxWidth, boxHeight);
FitChildInPixelBox(child, box);
child.ArrangePixel(box);
hOffset += minColWidth[column] + hSep;
}
return finalSize;
}
}

View File

@@ -5,7 +5,7 @@ using System.Diagnostics.Contracts;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.UserInterface.Controls
{
@@ -43,9 +43,7 @@ namespace Robust.Client.UserInterface.Controls
{
Name = "_v_scroll",
SizeFlagsVertical = SizeFlags.Fill,
SizeFlagsHorizontal = SizeFlags.ShrinkEnd
HorizontalAlignment = HAlignment.Right
};
AddChild(_scrollBar);
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
@@ -403,7 +401,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var size = Vector2.Zero;
if (ActualBackground != null)

View File

@@ -26,7 +26,7 @@ namespace Robust.Client.UserInterface.Controls
public Label()
{
SizeFlagsVertical = SizeFlags.ShrinkCenter;
VerticalAlignment = VAlignment.Center;
}
/// <summary>
@@ -40,7 +40,7 @@ namespace Robust.Client.UserInterface.Controls
{
_text = value;
_textDimensionCacheValid = false;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
{
_clipText = value;
RectClipContent = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -209,7 +209,7 @@ namespace Robust.Client.UserInterface.Controls
Fill = 3
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (!_textDimensionCacheValid)
{
@@ -267,7 +267,7 @@ namespace Robust.Client.UserInterface.Controls
continue;
}
_cachedTextWidths[_cachedTextWidths.Count-1] += metrics.Value.Advance;
_cachedTextWidths[^1] += metrics.Value.Advance;
}
}

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_textures = value;
CalculateMinimumSize();
InvalidateMeasure();
}
}
@@ -43,7 +43,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_textureScale = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -60,7 +60,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_canShrink = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -148,7 +148,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (_textures.Count == 0 || CanShrink)
{

View File

@@ -1,6 +1,8 @@
using System;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
{
@@ -17,6 +19,8 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public const float AnchorEnd = 1;
[ViewVariables(VVAccess.ReadWrite)] public bool Debug { get; set; }
public static readonly AttachedProperty MarginLeftProperty = AttachedProperty.Create("MarginLeft",
typeof(LayoutContainer), typeof(float), changed: LayoutPropertyChangedCallback);
@@ -47,6 +51,9 @@ namespace Robust.Client.UserInterface.Controls
public static readonly AttachedProperty GrowVerticalProperty = AttachedProperty.Create("GrowVertical",
typeof(LayoutContainer), typeof(GrowDirection), changed: LayoutPropertyChangedCallback);
public static readonly AttachedProperty<bool> DebugProperty = AttachedProperty<bool>.Create("Debug",
typeof(LayoutContainer));
public static void SetMarginLeft(Control control, float value)
{
@@ -112,16 +119,10 @@ namespace Robust.Client.UserInterface.Controls
SetMarginBottom(control, diffY + control.GetValue<float>(MarginBottomProperty));
}
public static void SetSize(Control control, Vector2 size)
[Obsolete("Change SetSize on the control instead.")]
public new static void SetSize(Control control, Vector2 size)
{
var (diffX, diffY) = size - control.Size;
// This is just to make subsequent set calls work correctly.
// It should get reset to this exact value next update either way.
control.Size = size;
SetMarginRight(control, diffX + control.GetValue<float>(MarginRightProperty));
SetMarginBottom(control, diffY + control.GetValue<float>(MarginBottomProperty));
control.SetSize = size;
}
/// <summary>
@@ -297,8 +298,9 @@ namespace Robust.Client.UserInterface.Controls
LayoutPresetMode resizeMode = LayoutPresetMode.MinSize,
int margin = 0)
{
control.Measure(Vector2.Infinity);
var newSize = control.Size;
var minSize = control.CombinedMinimumSize;
var minSize = control.DesiredSize;
if ((resizeMode & LayoutPresetMode.KeepWidth) == 0)
{
newSize = new Vector2(minSize.X, newSize.Y);
@@ -445,39 +447,131 @@ namespace Robust.Client.UserInterface.Controls
control.SetValue(MarginBottomProperty, marginBottom);
}
protected override void LayoutUpdateOverride()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var min = Vector2.Zero;
var uiScale = UIScale;
foreach (var child in Children)
{
var growH = child.GetValue<GrowDirection>(GrowHorizontalProperty);
var growV = child.GetValue<GrowDirection>(GrowVerticalProperty);
var anchorMargins = CalcAnchorMargins(availableSize, uiScale, child);
var size = availableSize;
if (growH == GrowDirection.Constrain)
size.X = anchorMargins.Width / uiScale;
if (growV == GrowDirection.Constrain)
size.Y = anchorMargins.Height / uiScale;
child.Measure(size);
min = Vector2.ComponentMax(min, child.DesiredSize);
}
return min;
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
foreach (var child in Children)
{
child.Arrange(CalcChildRect(finalSize, UIScale, child, out _));
}
return finalSize;
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (!Debug)
return;
var (pSizeX, pSizeY) = PixelSize;
foreach (var child in Children)
{
var anchorLeft = child.GetValue<float>(AnchorLeftProperty);
var anchorTop = child.GetValue<float>(AnchorTopProperty);
var anchorRight = child.GetValue<float>(AnchorRightProperty);
var anchorBottom = child.GetValue<float>(AnchorBottomProperty);
if (!child.GetValue(DebugProperty))
{
continue;
}
var marginLeft = child.GetValue<float>(MarginLeftProperty) * UIScale;
var marginTop = child.GetValue<float>(MarginTopProperty) * UIScale;
var marginRight = child.GetValue<float>(MarginRightProperty) * UIScale;
var marginBottom = child.GetValue<float>(MarginBottomProperty) * UIScale;
var rect = CalcChildRect(Size, UIScale, child, out var anchorSize);
var growHorizontal = child.GetValue<GrowDirection>(GrowHorizontalProperty);
var growVertical = child.GetValue<GrowDirection>(GrowVerticalProperty);
var left = rect.Left * UIScale;
var right = rect.Right * UIScale;
var top = rect.Top * UIScale;
var bottom = rect.Bottom * UIScale;
// Calculate where the control "wants" to be by its anchors/margins.
var left = anchorLeft * pSizeX + marginLeft;
var top = anchorTop * pSizeY + marginTop;
var right = anchorRight * pSizeX + marginRight;
var bottom = anchorBottom * pSizeY + marginBottom;
DrawVLine(anchorSize.Left, Color.Pink);
DrawVLine(anchorSize.Right, Color.Green);
DrawHLine(anchorSize.Top, Color.Pink);
DrawHLine(anchorSize.Bottom, Color.Green);
var (wSizeX, wSizeY) = (right - left, bottom - top);
var (minSizeX, minSizeY) = child.CombinedPixelMinimumSize;
/*
DrawVLine(left, Color.Orange);
DrawVLine(right, Color.Blue);
DrawHLine(top, Color.Orange);
DrawHLine(bottom, Color.Blue);
*/
HandleLayoutOverflow(growHorizontal, minSizeX, left, wSizeX, out var posX, out var sizeX);
HandleLayoutOverflow(growVertical, minSizeY, top, wSizeY, out var posY, out var sizeY);
child.Position = new Vector2(posX, posY) / UserInterfaceManager.UIScale;
child.Size = new Vector2(sizeX, sizeY) / UserInterfaceManager.UIScale;
handle.DrawRect(new UIBox2(left, top, right, bottom), Color.Red, false);
}
void DrawVLine(float x, Color color)
{
handle.DrawLine((x, 0), (x, pSizeY), color);
}
void DrawHLine(float y, Color color)
{
handle.DrawLine((0, y), (pSizeX, y), color);
}
}
private static UIBox2 CalcAnchorMargins(Vector2 ourSize, float uiScale, Control child)
{
var (pSizeX, pSizeY) = ourSize * uiScale;
var anchorLeft = child.GetValue<float>(AnchorLeftProperty);
var anchorTop = child.GetValue<float>(AnchorTopProperty);
var anchorRight = child.GetValue<float>(AnchorRightProperty);
var anchorBottom = child.GetValue<float>(AnchorBottomProperty);
var marginLeft = child.GetValue<float>(MarginLeftProperty) * uiScale;
var marginTop = child.GetValue<float>(MarginTopProperty) * uiScale;
var marginRight = child.GetValue<float>(MarginRightProperty) * uiScale;
var marginBottom = child.GetValue<float>(MarginBottomProperty) * uiScale;
var left = anchorLeft * pSizeX + marginLeft;
var top = anchorTop * pSizeY + marginTop;
var right = anchorRight * pSizeX + marginRight;
var bottom = anchorBottom * pSizeY + marginBottom;
// Yes, this can return boxes with left > right (and top > bottom).
// This is "intentional", see comment in CalcChildRect.
return new UIBox2(left, top, right, bottom);
}
private static UIBox2 CalcChildRect(Vector2 ourSize, float uiScale, Control child, out UIBox2 anchorSize)
{
// Calculate where the control "wants" to be by its anchors/margins.
var growHorizontal = child.GetValue<GrowDirection>(GrowHorizontalProperty);
var growVertical = child.GetValue<GrowDirection>(GrowVerticalProperty);
anchorSize = CalcAnchorMargins(ourSize, uiScale, child);
// This intentionally results in negatives if the right bound is < the left bound.
// Which then causes HandleLayoutOverflow to CORRECTLY work from the right bound instead.
var (wSizeX, wSizeY) = (anchorSize.Right - anchorSize.Left, anchorSize.Bottom - anchorSize.Top);
var (minSizeX, minSizeY) = child.DesiredPixelSize;
HandleLayoutOverflow(growHorizontal, minSizeX, anchorSize.Left, wSizeX, out var posX, out var sizeX);
HandleLayoutOverflow(growVertical, minSizeY, anchorSize.Top, wSizeY, out var posY, out var sizeY);
return UIBox2.FromDimensions(posX / uiScale, posY / uiScale, sizeX / uiScale, sizeY / uiScale);
}
private static void HandleLayoutOverflow(GrowDirection direction, float minSize, float wPos, float wSize,
@@ -485,7 +579,7 @@ namespace Robust.Client.UserInterface.Controls
out float size)
{
var overflow = minSize - wSize;
if (overflow <= 0)
if (overflow <= 0 || direction == GrowDirection.Constrain)
{
pos = wPos;
size = wSize;
@@ -514,7 +608,7 @@ namespace Robust.Client.UserInterface.Controls
{
if (owner.Parent is LayoutContainer container)
{
container.UpdateLayout();
container.InvalidateArrange();
}
}
@@ -537,7 +631,12 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// The control will expand on all axes equally to reach its minimum size.
/// </summary>
Both
Both,
/// <summary>
/// The control will not be allowed to grow on this axis.
/// </summary>
Constrain,
}
/// <seealso cref="Control.SetMarginsPreset" />

View File

@@ -242,18 +242,22 @@ namespace Robust.Client.UserInterface.Controls
}
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var font = _getFont();
var style = _getStyleBox();
return new Vector2(0, font.GetHeight(UIScale) / UIScale) + style.MinimumSize / UIScale;
}
protected override void LayoutUpdateOverride()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var style = _getStyleBox();
FitChildInPixelBox(_renderBox, (UIBox2i) style.GetContentBox(PixelSizeBox));
_renderBox.ArrangePixel(
(UIBox2i) style.GetContentBox(
UIBox2.FromDimensions(Vector2.Zero, finalSize * UIScale)));
return finalSize;
}
protected internal override void TextEntered(GUITextEventArgs args)

View File

@@ -1,7 +1,9 @@
using System;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls
{
[Obsolete("Set Margin directly")]
public class MarginContainer : Container
{
public int? MarginBottomOverride { get; set; }
@@ -9,36 +11,41 @@ namespace Robust.Client.UserInterface.Controls
public int? MarginRightOverride { get; set; }
public int? MarginLeftOverride { get; set; }
protected override void LayoutUpdateOverride()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var top = MarginTopOverride ?? 0;
var bottom = MarginBottomOverride ?? 0;
var left = MarginLeftOverride ?? 0;
var right = MarginRightOverride ?? 0;
var box = UIBox2.FromDimensions(left, top, Width - right - left, Height - bottom - top);
var margin = GetMargin();
var availWithoutMargin = margin.Deflate(availableSize);
var max = Vector2.Zero;
foreach (var child in Children)
{
FitChildInBox(child, box);
child.Measure(availWithoutMargin);
max = Vector2.ComponentMax(max, child.DesiredSize);
}
return margin.Inflate(max);
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var box = GetMargin().Deflate(UIBox2.FromDimensions(Vector2.Zero, finalSize));
foreach (var child in Children)
{
child.Arrange(box);
}
return finalSize;
}
private Thickness GetMargin()
{
var top = MarginTopOverride ?? 0;
var bottom = MarginBottomOverride ?? 0;
var left = MarginLeftOverride ?? 0;
var right = MarginRightOverride ?? 0;
var childMinSize = Vector2.Zero;
foreach (var child in Children)
{
childMinSize = Vector2.ComponentMax(child.CombinedMinimumSize, childMinSize);
}
return childMinSize + (left + right, top + bottom);
var margin = new Thickness(left, top, right, bottom);
return margin;
}
}
}

View File

@@ -26,7 +26,7 @@ namespace Robust.Client.UserInterface.Controls
{
Children =
{
(_popupVBox = new VBoxContainer {CustomMinimumSize = (300, 0)})
(_popupVBox = new VBoxContainer {MinSize = (300, 0)})
}
};
_popup.OnPopupHide += PopupHidden;
@@ -113,7 +113,7 @@ namespace Robust.Client.UserInterface.Controls
break;
case MenuSeparator _:
var control = new Control {CustomMinimumSize = (0, 6)};
var control = new Control {MinSize = (0, 6)};
container.AddChild(control);
break;
}

View File

@@ -71,14 +71,14 @@ namespace Robust.Client.UserInterface.Controls
_label = new Label
{
StyleClasses = { StyleClassOptionButton },
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
};
hBox.AddChild(_label);
var textureRect = new TextureRect
{
StyleClasses = { StyleClassOptionTriangle },
SizeFlagsVertical = SizeFlags.ShrinkCenter,
VerticalAlignment = VAlignment.Center,
};
hBox.AddChild(textureRect);
}
@@ -112,7 +112,8 @@ namespace Robust.Client.UserInterface.Controls
if (show)
{
var globalPos = GlobalPosition;
var (minX, minY) = _popupVBox.CombinedMinimumSize;
_popupVBox.Measure(Vector2.Infinity);
var (minX, minY) = _popupVBox.DesiredSize;
var box = UIBox2.FromDimensions(globalPos, (Math.Max(minX, Width), minY));
UserInterfaceManager.ModalRoot.AddChild(_popup);
_popup.Open(box);

View File

@@ -15,15 +15,36 @@ namespace Robust.Client.UserInterface.Controls
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly Label _label;
private readonly TextureRect _triangle;
public int ItemCount => _buttonData.Count;
/// <summary>
/// If true, hides the triangle that normally appears to the right of the button label
/// </summary>
public bool HideTriangle
{
get => _hideTriangle;
set
{
_hideTriangle = value;
_triangle.Visible = !_hideTriangle;
}
}
private bool _hideTriangle;
/// <summary>
/// StyleClasses to apply to the options that popup when clicking this button.
/// </summary>
public ICollection<string> OptionStyleClasses { get; }
public event Action<ItemSelectedEventArgs>? OnItemSelected;
public string Prefix { get; set; }
public OptionButton()
{
OptionStyleClasses = new List<string>();
AddStyleClass(StyleClassButton);
Prefix = "";
OnPressed += OnPressedInternal;
@@ -39,16 +60,17 @@ namespace Robust.Client.UserInterface.Controls
_label = new Label
{
StyleClasses = { StyleClassOptionButton },
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
};
hBox.AddChild(_label);
var textureRect = new TextureRect
_triangle = new TextureRect
{
StyleClasses = { StyleClassOptionTriangle },
SizeFlagsVertical = SizeFlags.ShrinkCenter,
VerticalAlignment = VAlignment.Center,
Visible = !HideTriangle
};
hBox.AddChild(textureRect);
hBox.AddChild(_triangle);
}
public void AddItem(Texture icon, string label, int? id = null)
@@ -73,6 +95,10 @@ namespace Robust.Client.UserInterface.Controls
Text = label,
ToggleMode = true
};
foreach (var styleClass in OptionStyleClasses)
{
button.AddStyleClass(styleClass);
}
button.OnPressed += ButtonOnPressed;
var data = new ButtonData(label, button)
{
@@ -92,7 +118,8 @@ namespace Robust.Client.UserInterface.Controls
if (show)
{
var globalPos = GlobalPosition;
var (minX, minY) = _popupVBox.CombinedMinimumSize;
_popupVBox.Measure(Vector2.Infinity);
var (minX, minY) = _popupVBox.DesiredSize;
var box = UIBox2.FromDimensions(globalPos, (Math.Max(minX, Width), minY));
UserInterfaceManager.ModalRoot.AddChild(_popup);
_popup.Open(box);
@@ -170,6 +197,10 @@ namespace Robust.Client.UserInterface.Controls
}
}
/// <summary>
/// Select by index rather than id. Throws exception if item with that index
/// not in this control.
/// </summary>
public void Select(int idx)
{
if (_idMap.TryGetValue(SelectedId, out var prevIdx))
@@ -182,11 +213,29 @@ namespace Robust.Client.UserInterface.Controls
data.Button.Pressed = true;
}
/// <summary>
/// Select by index rather than id.
/// </summary>
/// <returns>false if item with that index not in this control</returns>
public bool TrySelect(int idx)
{
if (idx < 0 || idx >= _buttonData.Count) return false;
Select(idx);
return true;
}
/// throws exception if item with this ID is not in this control
public void SelectId(int id)
{
Select(GetIdx(id));
}
/// <returns>false if item with id not in this control</returns>
public bool TrySelectId(int id)
{
return _idMap.TryGetValue(id, out var idx) && TrySelect(idx);
}
public int GetIdx(int id)
{
return _idMap[id];

View File

@@ -31,8 +31,7 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar = new VScrollBar
{
Name = "_v_scroll",
SizeFlagsVertical = SizeFlags.Fill,
SizeFlagsHorizontal = SizeFlags.ShrinkEnd
HorizontalAlignment = HAlignment.Right
};
AddChild(_scrollBar);
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
@@ -44,7 +43,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_styleBoxOverride = value;
MinimumSizeChanged();
InvalidateMeasure();
_invalidateEntries();
}
}
@@ -168,7 +167,7 @@ namespace Robust.Client.UserInterface.Controls
_invalidateEntries();
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return _getStyleBox()?.MinimumSize ?? Vector2.Zero;
}

View File

@@ -17,26 +17,32 @@ namespace Robust.Client.UserInterface.Controls
style?.Draw(handle, PixelSizeBox);
}
protected override void LayoutUpdateOverride()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var contentBox = _getStyleBox()?.GetContentBox(PixelSizeBox) ?? PixelSizeBox;
foreach (var child in Children)
{
FitChildInPixelBox(child, (UIBox2i) contentBox);
}
}
protected override Vector2 CalculateMinimumSize()
{
var styleSize = _getStyleBox()?.MinimumSize ?? Vector2.Zero;
var styleSize = (_getStyleBox()?.MinimumSize ?? Vector2.Zero) / UIScale;
var measureSize = Vector2.ComponentMax(availableSize - styleSize, Vector2.Zero);
var childSize = Vector2.Zero;
foreach (var child in Children)
{
childSize = Vector2.ComponentMax(childSize, child.CombinedMinimumSize);
child.Measure(measureSize);
childSize = Vector2.ComponentMax(childSize, child.DesiredSize);
}
return styleSize / UIScale + childSize;
return styleSize + childSize;
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var pixelSize = finalSize * UIScale;
var ourSize = UIBox2.FromDimensions(Vector2.Zero, pixelSize);
var contentBox = _getStyleBox()?.GetContentBox(ourSize) ?? ourSize;
foreach (var child in Children)
{
child.ArrangePixel((UIBox2i) contentBox);
}
return finalSize;
}
[System.Diagnostics.Contracts.Pure]

View File

@@ -30,7 +30,7 @@ namespace Robust.Client.UserInterface.Controls
PopupContainer.SetAltOrigin(this, altPos);
_desiredSize = box.Value.Size;
MinimumSizeChanged();
InvalidateMeasure();
}
Visible = true;
@@ -52,9 +52,11 @@ namespace Robust.Client.UserInterface.Controls
OnPopupHide?.Invoke();
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return Vector2.ComponentMax(_desiredSize, base.CalculateMinimumSize());
return Vector2.ComponentMax(
_desiredSize,
base.MeasureOverride(Vector2.ComponentMax(availableSize, _desiredSize)));
}
}
}

View File

@@ -54,15 +54,15 @@ namespace Robust.Client.UserInterface.Controls
{
if (owner.Parent is PopupContainer container)
{
container.UpdateLayout();
container.InvalidateArrange();
}
}
protected override void LayoutUpdateOverride()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
foreach (var child in Children)
{
var size = child.CombinedMinimumSize;
var size = child.DesiredSize;
var offset = child.GetValue<Vector2>(PopupOriginProperty);
var altPos = child.GetValue<Vector2?>(AltOriginProperty);
@@ -105,22 +105,25 @@ namespace Robust.Client.UserInterface.Controls
offset -= (0, offset.Y);
}
FitChildInBox(child, UIBox2.FromDimensions(offset, size));
child.Arrange(UIBox2.FromDimensions(offset, size));
}
return finalSize;
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
// Do NOT inherit minimum size from contents!
// Just clip 'em.
return (0, 0);
// Measure to availableSize so that child controls never get too large to fit the whole screen.
base.MeasureOverride(availableSize);
return availableSize;
}
protected override void Resized()
{
base.Resized();
UpdateLayout();
InvalidateArrange();
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_backgroundStyleBoxOverride = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -28,7 +28,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_foregroundStyleBoxOverride = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -76,7 +76,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var bgSize = _getBackground()?.MinimumSize ?? Vector2.Zero;
var fgSize = _getForeground()?.MinimumSize ?? Vector2.Zero;

View File

@@ -11,13 +11,11 @@ namespace Robust.Client.UserInterface.Controls
private FormattedMessage? _message;
private RichTextEntry _entry;
public float? MaxWidth { get; set; }
public void SetMessage(FormattedMessage message)
{
_message = message;
_entry = new RichTextEntry(_message);
_updateEntry();
InvalidateMeasure();
}
public void SetMessage(string message)
@@ -27,42 +25,17 @@ namespace Robust.Client.UserInterface.Controls
SetMessage(msg);
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (_message == null)
{
return Vector2.Zero;
}
var width = 0f;
if (MaxWidth.HasValue)
{
width = _entry.Width / UIScale;
}
return (width, _entry.Height / UIScale);
}
private void _updateEntry()
{
var font = _getFont();
_entry.Update(font, availableSize.X * UIScale, UIScale);
if (_message != null)
{
var oldHeight = _entry.Height;
var oldWidth = _entry.Width;
_entry.Update(font, (MaxWidth ?? Width) * UIScale, UIScale);
if (oldHeight != _entry.Height || MaxWidth != null && _entry.Width != oldWidth)
{
MinimumSizeChanged();
}
}
}
protected override void StylePropertiesChanged()
{
base.StylePropertiesChanged();
_updateEntry();
return (_entry.Width / UIScale, _entry.Height / UIScale);
}
protected internal override void Draw(DrawingHandleScreen handle)
@@ -77,13 +50,6 @@ namespace Robust.Client.UserInterface.Controls
_entry.Draw(handle, _getFont(), SizeBox, 0, new Stack<FormattedMessage.Tag>(), UIScale);
}
protected override void Resized()
{
base.Resized();
_updateEntry();
}
[Pure]
private Font _getFont()
{

View File

@@ -129,13 +129,13 @@ namespace Robust.Client.UserInterface.Controls
if (_grabData == null)
{
var box = _getGrabberBox();
_isHovered = box.Contains(args.RelativePosition);
_isHovered = box.Contains(args.RelativePixelPosition);
_updatePseudoClass();
return;
}
var (grabPos, grabValue) = _grabData.Value;
var (grabRelX, grabRelY) = args.RelativePosition - grabPos;
var (grabRelX, grabRelY) = args.RelativePixelPosition - grabPos;
float moved;
if (_orientation == OrientationMode.Horizontal)
@@ -219,7 +219,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return _getGrabberStyleBox()?.MinimumSize ?? Vector2.Zero;
}

View File

@@ -26,14 +26,14 @@ namespace Robust.Client.UserInterface.Controls
_hScrollBar = new HScrollBar
{
Visible = false,
SizeFlagsVertical = SizeFlags.ShrinkEnd,
SizeFlagsHorizontal = SizeFlags.Fill
VerticalAlignment = VAlignment.Bottom,
HorizontalAlignment = HAlignment.Stretch
};
_vScrollBar = new VScrollBar
{
Visible = false,
SizeFlagsVertical = SizeFlags.Fill,
SizeFlagsHorizontal = SizeFlags.ShrinkEnd
VerticalAlignment = VAlignment.Stretch,
HorizontalAlignment = HAlignment.Right
};
AddChild(_hScrollBar);
AddChild(_vScrollBar);
@@ -47,7 +47,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_vScrollEnabled = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -57,17 +57,50 @@ namespace Robust.Client.UserInterface.Controls
set
{
_hScrollEnabled = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
protected override void LayoutUpdateOverride()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (_vScrollEnabled)
{
_vScrollBar.Measure(availableSize);
availableSize.X -= _vScrollBar.DesiredSize.X;
}
if (_hScrollEnabled)
{
_hScrollBar.Measure(availableSize);
availableSize.Y -= _hScrollBar.DesiredSize.Y;
}
var constraint = new Vector2(
_hScrollEnabled ? float.PositiveInfinity : availableSize.X,
_vScrollEnabled ? float.PositiveInfinity : availableSize.Y);
var size = Vector2.Zero;
foreach (var child in Children)
{
child.Measure(constraint);
size = Vector2.ComponentMax(size, child.DesiredSize);
}
// Unlike WPF/Avalonia we report ZERO here instead of available size.
// This is to fix a bunch of jank with e.g. BoxContainer.
// Tbh this might be a mistake.
// DockPanel when.
return Vector2.Zero;
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
if (_vScrollBar?.Parent == null || _hScrollBar?.Parent == null)
{
// Just don't run this before we're properly initialized.
return;
return Vector2.Zero;
}
var maxChildMinSize = Vector2.Zero;
foreach (var child in Children)
@@ -77,31 +110,31 @@ namespace Robust.Client.UserInterface.Controls
continue;
}
maxChildMinSize = Vector2.ComponentMax(child.CombinedMinimumSize, maxChildMinSize);
maxChildMinSize = Vector2.ComponentMax(child.DesiredSize, maxChildMinSize);
}
var (cWidth, cHeight) = maxChildMinSize;
var hBarSize = _hScrollBar.CombinedMinimumSize.Y;
var vBarSize = _vScrollBar.CombinedMinimumSize.X;
var hBarSize = _hScrollBar.DesiredSize.Y;
var vBarSize = _vScrollBar.DesiredSize.X;
var (sWidth, sHeight) = Size;
var (sWidth, sHeight) = finalSize;
try
{
// Suppress events to avoid weird recursion.
_suppressScrollValueChanged = true;
if (Width < cWidth)
if (sWidth < cWidth && _hScrollEnabled)
{
sHeight -= hBarSize;
}
if (Height < cHeight)
if (sHeight < cHeight && _vScrollEnabled)
{
sWidth -= vBarSize;
}
if (sWidth < cWidth)
if (sWidth < cWidth && _hScrollEnabled)
{
_hScrollBar.Visible = _hScrollVisible = true;
_hScrollBar.Page = sWidth;
@@ -112,7 +145,7 @@ namespace Robust.Client.UserInterface.Controls
_hScrollBar.Visible = _hScrollVisible = false;
}
if (sHeight < cHeight)
if (sHeight < cHeight && _vScrollEnabled)
{
_vScrollBar.Visible = _vScrollVisible = true;
_vScrollBar.Page = sHeight;
@@ -129,10 +162,19 @@ namespace Robust.Client.UserInterface.Controls
_suppressScrollValueChanged = false;
}
var sSize = (sWidth, sHeight);
if (_vScrollVisible)
{
_vScrollBar.Arrange(UIBox2.FromDimensions(Vector2.Zero, finalSize));
}
FitChildInPixelBox(_vScrollBar, PixelSizeBox);
FitChildInPixelBox(_hScrollBar, PixelSizeBox);
if (_hScrollVisible)
{
_hScrollBar.Arrange(UIBox2.FromDimensions(Vector2.Zero, finalSize));
}
var realFinalSize = (
_hScrollEnabled ? Math.Max(cWidth, sWidth) : sWidth,
_vScrollEnabled ? Math.Max(cHeight, sHeight) : sHeight);
foreach (var child in Children)
{
@@ -142,47 +184,11 @@ namespace Robust.Client.UserInterface.Controls
}
var position = -_getScrollValue();
var rect = UIBox2.FromDimensions(position, Vector2.ComponentMax(child.CombinedMinimumSize, sSize));
FitChildInBox(child, rect);
}
}
protected override Vector2 CalculateMinimumSize()
{
var totalX = 0f;
var totalY = 0f;
foreach (var child in Children)
{
if (child == _hScrollBar || child == _vScrollBar)
{
continue;
}
if (!_vScrollEnabled)
{
totalY = Math.Max(totalY, child.CombinedMinimumSize.Y);
}
if (!_hScrollEnabled)
{
totalX = Math.Max(totalX, child.CombinedMinimumSize.X);
}
var rect = UIBox2.FromDimensions(position, realFinalSize);
child.Arrange(rect);
}
if (_vScrollEnabled)
{
totalX += _vScrollBar.CombinedMinimumSize.X;
totalY = Math.Max(_vScrollBar.CombinedMinimumSize.Y, totalY);
}
if (_hScrollEnabled)
{
totalY += _hScrollBar.CombinedMinimumSize.Y;
totalX = Math.Max(_vScrollBar.CombinedMinimumSize.X, totalX);
}
return new Vector2(totalX, totalY);
return finalSize;
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
@@ -223,10 +229,12 @@ namespace Robust.Client.UserInterface.Controls
{
h = 0;
}
if (!_vScrollVisible)
{
v = 0;
}
return new Vector2(h, v);
}
@@ -237,7 +245,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
UpdateLayout();
InvalidateArrange();
}
}
}

View File

@@ -109,7 +109,7 @@ namespace Robust.Client.UserInterface.Controls
{
var ratio = GetAsRatio();
var margin = (Width - _grabber.CombinedMinimumSize.X) * ratio + _grabber.CombinedMinimumSize.X / 2;
var margin = (Width - _grabber.DesiredSize.X) * ratio + _grabber.DesiredSize.X / 2;
SetMarginRight(_fillPanel, margin);
SetMarginLeft(_grabber, margin);
SetMarginRight(_grabber, margin);
@@ -156,7 +156,7 @@ namespace Robust.Client.UserInterface.Controls
private void HandlePositionChange(Vector2 position)
{
var grabberWidth = _grabber.CombinedMinimumSize.X;
var grabberWidth = _grabber.DesiredSize.X;
var ratio = (position.X - grabberWidth / 2) / (Width - grabberWidth);
SetAsRatio(ratio);
}

View File

@@ -58,8 +58,8 @@ namespace Robust.Client.UserInterface.Controls
_lineEdit = new LineEdit
{
CustomMinimumSize = new Vector2(40, 0),
SizeFlagsHorizontal = SizeFlags.FillExpand
MinSize = new Vector2(40, 0),
HorizontalExpand = true
};
AddChild(_lineEdit);

View File

@@ -31,8 +31,9 @@ namespace Robust.Client.UserInterface.Controls
// min / max x and y extents in relative virtual pixels of where the split can go regardless
// of anything else.
private float SplitMin => SplitWidth + SplitEdgeSeparation;
private float SplitMax => Vertical ? Height - (SplitWidth + SplitEdgeSeparation) :
Width - (SplitWidth + SplitEdgeSeparation);
private float SplitMax =>
Vertical ? Height - (SplitWidth + SplitEdgeSeparation) : Width - (SplitWidth + SplitEdgeSeparation);
public SplitContainer()
{
@@ -56,11 +57,10 @@ namespace Robust.Client.UserInterface.Controls
_splitCenter = ClampSplitCenter(newOffset);
DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize;
ForceRunLayoutUpdate();
InvalidateArrange();
}
else
{
// on mouseover, check if they are over the split and change the cursor accordingly
var cursor = CursorShape.Arrow;
if (CanDragAt(args.RelativePosition))
@@ -124,37 +124,32 @@ namespace Robust.Client.UserInterface.Controls
var first = GetChild(0);
var second = GetChild(1);
firstMinSize ??= (Vertical ? first.CombinedMinimumSize.Y : first.CombinedMinimumSize.X);
secondMinSize ??= (Vertical ? second.CombinedMinimumSize.Y : second.CombinedMinimumSize.X);
firstMinSize ??= (Vertical ? first.DesiredSize.Y : first.DesiredSize.X);
secondMinSize ??= (Vertical ? second.DesiredSize.Y : second.DesiredSize.X);
var size = Vertical ? Height : Width;
splitCenter = MathHelper.Clamp(splitCenter, firstMinSize.Value, size - (secondMinSize.Value + SplitWidth));
splitCenter = MathHelper.Clamp(splitCenter, firstMinSize.Value,
size - (secondMinSize.Value + SplitWidth));
}
return splitCenter;
}
protected override void LayoutUpdateOverride()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
base.LayoutUpdateOverride();
if (ChildCount != 2)
{
return;
return finalSize;
}
var first = GetChild(0);
var second = GetChild(1);
var firstExpand = Vertical
? (first.SizeFlagsVertical & SizeFlags.Expand) != 0
: (first.SizeFlagsHorizontal & SizeFlags.Expand) != 0;
var secondExpand = Vertical
? (second.SizeFlagsVertical & SizeFlags.Expand) != 0
: (second.SizeFlagsHorizontal & SizeFlags.Expand) != 0;
var firstExpand = Vertical ? first.VerticalExpand : first.HorizontalExpand;
var secondExpand = Vertical ? second.VerticalExpand : second.HorizontalExpand;
var firstMinSize = Vertical ? first.CombinedMinimumSize.Y : first.CombinedMinimumSize.X;
var secondMinSize = Vertical ? second.CombinedMinimumSize.Y : second.CombinedMinimumSize.X;
var firstMinSize = Vertical ? first.DesiredSize.Y : first.DesiredSize.X;
var secondMinSize = Vertical ? second.DesiredSize.Y : second.DesiredSize.X;
var size = Vertical ? Height : Width;
@@ -181,24 +176,27 @@ namespace Robust.Client.UserInterface.Controls
_splitCenter = firstMinSize;
}
_splitCenter += MathHelper.Clamp(0f, firstMinSize - _splitCenter, size - secondMinSize - SplitWidth - _splitCenter);
_splitCenter += MathHelper.Clamp(0f, firstMinSize - _splitCenter,
size - secondMinSize - SplitWidth - _splitCenter);
break;
}
}
if (Vertical)
{
FitChildInBox(first, new UIBox2(0, 0, Width, _splitCenter));
FitChildInBox(second, new UIBox2(0, _splitCenter + SplitWidth, Width, Height));
first.Arrange(new UIBox2(0, 0, Width, _splitCenter));
second.Arrange(new UIBox2(0, _splitCenter + SplitWidth, Width, Height));
}
else
{
FitChildInBox(first, new UIBox2(0, 0, _splitCenter, Height));
FitChildInBox(second, new UIBox2(_splitCenter + SplitWidth, 0, Width, Height));
first.Arrange(new UIBox2(0, 0, _splitCenter, Height));
second.Arrange(new UIBox2(_splitCenter + SplitWidth, 0, Width, Height));
}
return finalSize;
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (ChildCount != 2)
{
@@ -208,8 +206,12 @@ namespace Robust.Client.UserInterface.Controls
var first = GetChild(0);
var second = GetChild(1);
var (firstSizeX, firstSizeY) = first.CombinedMinimumSize;
var (secondSizeX, secondSizeY) = second.CombinedMinimumSize;
// TODO: Probably bad implementation with the new WPF layout.
first.Measure(availableSize);
second.Measure(availableSize);
var (firstSizeX, firstSizeY) = first.DesiredSize;
var (secondSizeX, secondSizeY) = second.DesiredSize;
if (Vertical)
{
@@ -236,6 +238,7 @@ namespace Robust.Client.UserInterface.Controls
/// Don't allow user to move the split.
/// </summary>
NotResizable = -1,
/// <summary>
/// User can resize the split but can't shrink either child
/// beyond its minimum size.
@@ -255,6 +258,7 @@ namespace Robust.Client.UserInterface.Controls
/// Automatically adjust the split based on the width of the children
/// </summary>
Auto = 0,
/// <summary>
/// Manually adjust the split by dragging it
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_scale = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -34,7 +34,7 @@ namespace Robust.Client.UserInterface.Controls
RectClipContent = true;
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
// TODO: make this not hardcoded.
// It'll break on larger things.

View File

@@ -47,7 +47,7 @@ namespace Robust.Client.UserInterface.Controls
GetChild(old).Visible = false;
var newSelected = GetChild(value);
newSelected.Visible = true;
_fixChildMargins(newSelected);
InvalidateMeasure();
OnTabChanged?.Invoke(value);
}
@@ -59,7 +59,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_tabsVisible = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -124,7 +124,6 @@ namespace Robust.Client.UserInterface.Controls
{
// This is our first child so it must always be visible.
newChild.Visible = true;
_fixChildMargins(newChild);
}
else
{
@@ -212,44 +211,52 @@ namespace Robust.Client.UserInterface.Controls
}
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var total = Vector2i.Zero;
var headerSize = Vector2.Zero;
if (TabsVisible)
{
headerSize = (0, _getHeaderSize() / UIScale);
}
var panel = _getPanel();
var panelSize = (panel?.MinimumSize ?? Vector2.Zero) / UIScale;
var contentsSize = availableSize - headerSize - panelSize;
var total = Vector2.Zero;
foreach (var child in Children)
{
if (child.Visible)
{
total = Vector2i.ComponentMax(child.CombinedPixelMinimumSize, total);
child.Measure(contentsSize);
total = Vector2.ComponentMax(child.DesiredSize, total);
}
}
if (TabsVisible)
{
total += (0, _getHeaderSize());
}
var panel = _getPanel();
total += (Vector2i)(panel?.MinimumSize ?? Vector2.Zero);
return total / UIScale;
return total + headerSize + panelSize;
}
private void _fixChildMargins(Control child)
{
FitChildInPixelBox(child, _getContentBox());
}
protected override void LayoutUpdateOverride()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
if (ChildCount == 0 || _currentTab >= ChildCount)
{
return;
return finalSize;
}
var headerSize = _getHeaderSize();
var panel = _getPanel();
var contentBox = new UIBox2i(0, headerSize, (int) (finalSize.X * UIScale), (int) (finalSize.Y * UIScale));
if (panel != null)
{
contentBox = (UIBox2i) panel.GetContentBox(contentBox);
}
var control = GetChild(_currentTab);
control.Visible = true;
_fixChildMargins(control);
control.ArrangePixel(contentBox);
return finalSize;
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
@@ -313,19 +320,6 @@ namespace Robust.Client.UserInterface.Controls
}
}
[System.Diagnostics.Contracts.Pure]
private UIBox2i _getContentBox()
{
var headerSize = _getHeaderSize();
var panel = _getPanel();
var panelBox = new UIBox2i(0, headerSize, PixelWidth, PixelHeight);
if (panel != null)
{
return (UIBox2i) panel.GetContentBox(panelBox);
}
return panelBox;
}
[System.Diagnostics.Contracts.Pure]
private int _getHeaderSize()
{

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_textureNormal = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -37,7 +37,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_scale = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -78,7 +78,7 @@ namespace Robust.Client.UserInterface.Controls
handle.DrawTextureRectRegion(texture, PixelSizeBox);
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var texture = TextureNormal;

View File

@@ -32,7 +32,7 @@ namespace Robust.Client.UserInterface.Controls
if (value?.Size != oldSize)
{
MinimumSizeChanged();
InvalidateMeasure();
}
}
}
@@ -49,7 +49,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_textureScale = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -66,7 +66,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_canShrink = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -223,7 +223,7 @@ namespace Robust.Client.UserInterface.Controls
KeepAspectCovered = 8
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
var texture = _texture;

View File

@@ -34,8 +34,8 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar = new VScrollBar
{
Name = "_v_scroll",
SizeFlagsVertical = SizeFlags.Fill,
SizeFlagsHorizontal = SizeFlags.ShrinkEnd
VerticalAlignment = VAlignment.Stretch,
HorizontalAlignment = HAlignment.Right
};
AddChild(_scrollBar);
}

View File

@@ -1,8 +1,10 @@
using System;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Client.UserInterface.CustomControls
{
@@ -125,34 +127,39 @@ namespace Robust.Client.UserInterface.CustomControls
}
else
{
var top = Rect.Top;
var bottom = Rect.Bottom;
var left = Rect.Left;
var right = Rect.Right;
var (minSizeX, minSizeY) = CombinedMinimumSize;
var (left, top) = Position;
var (right, bottom) = Position + SetSize;
if ((CurrentDrag & DragMode.Top) == DragMode.Top)
{
var maxY = bottom - minSizeY;
top = Math.Min(args.GlobalPosition.Y - DragOffsetTopLeft.Y, maxY);
top = Math.Min(args.GlobalPosition.Y - DragOffsetTopLeft.Y, bottom);
}
else if ((CurrentDrag & DragMode.Bottom) == DragMode.Bottom)
{
bottom = Math.Max(args.GlobalPosition.Y + DragOffsetBottomRight.Y, top + minSizeY);
bottom = Math.Max(args.GlobalPosition.Y + DragOffsetBottomRight.Y, top);
}
if ((CurrentDrag & DragMode.Left) == DragMode.Left)
{
var maxX = right - minSizeX;
left = Math.Min(args.GlobalPosition.X - DragOffsetTopLeft.X, maxX);
left = Math.Min(args.GlobalPosition.X - DragOffsetTopLeft.X, right);
}
else if ((CurrentDrag & DragMode.Right) == DragMode.Right)
{
right = Math.Max(args.GlobalPosition.X + DragOffsetBottomRight.X, left + minSizeX);
right = Math.Max(args.GlobalPosition.X + DragOffsetBottomRight.X, left);
}
var rect = new UIBox2(left, top, right, bottom);
LayoutContainer.SetPosition(this, rect.TopLeft);
LayoutContainer.SetSize(this, rect.Size);
SetSize = rect.Size;
/*
var timing = IoCManager.Resolve<IGameTiming>();
var l = GetValue<float>(LayoutContainer.MarginLeftProperty);
var t = GetValue<float>(LayoutContainer.MarginTopProperty);
Logger.Debug($"{timing.CurFrame}: {rect.TopLeft}/({l}, {t}), {rect.Size}/{SetSize}");
*/
}
}
@@ -215,7 +222,8 @@ namespace Robust.Client.UserInterface.CustomControls
{
if (_firstTimeOpened)
{
LayoutContainer.SetSize(this, CombinedMinimumSize);
Measure(Vector2.Infinity);
SetSize = DesiredSize;
Open();
// An explaination: The BadOpenGLVersionWindow was showing up off the top-left corner of the screen.
// Basically, if OpenCentered happens super-early, RootControl doesn't get time to layout children.
@@ -233,7 +241,8 @@ namespace Robust.Client.UserInterface.CustomControls
{
if (_firstTimeOpened)
{
LayoutContainer.SetSize(this, CombinedMinimumSize);
Measure(Vector2.Infinity);
SetSize = DesiredSize;
Open();
LayoutContainer.SetPosition(this, (0, (Parent!.Size.Y - Size.Y) / 2));
_firstTimeOpened = false;

View File

@@ -77,7 +77,7 @@ namespace Robust.Client.UserInterface.CustomControls
{
(Output = new OutputPanel
{
SizeFlagsVertical = SizeFlags.FillExpand,
VerticalExpand = true,
StyleBoxOverride = styleBox
}),
(CommandBar = new HistoryLineEdit {PlaceHolder = "Command Here"})

View File

@@ -27,11 +27,11 @@ namespace Robust.Client.UserInterface.CustomControls
{
IoCManager.InjectDependencies(this);
SizeFlagsHorizontal = SizeFlags.None;
HorizontalAlignment = HAlignment.Left;
_contents = new Label
{
FontColorShadowOverride = Color.Black,
FontColorShadowOverride = Color.Black
};
AddChild(_contents);
@@ -43,6 +43,7 @@ namespace Robust.Client.UserInterface.CustomControls
};
MouseFilter = _contents.MouseFilter = MouseFilterMode.Ignore;
MinWidth = 175;
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -70,8 +71,10 @@ namespace Robust.Client.UserInterface.CustomControls
}
else
{
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId), mouseWorldMap.Position);
tile = new TileRef(mouseWorldMap.MapId, GridId.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager), Tile.Empty);
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
mouseWorldMap.Position);
tile = new TileRef(mouseWorldMap.MapId, GridId.Invalid,
mouseGridPos.ToVector2i(_entityManager, _mapManager), Tile.Empty);
}
var controlHovered = UserInterfaceManager.CurrentlyHovered;
@@ -103,7 +106,8 @@ Mouse Pos:
{1}
{2}
EntId: {3}
GridID: {4}", playerScreen, playerWorldOffset, playerCoordinates, entityTransform.Owner.Uid, entityTransform.GridID);
GridID: {4}", playerScreen, playerWorldOffset, playerCoordinates, entityTransform.Owner.Uid,
entityTransform.GridID);
}
if (controlHovered != null)
@@ -112,7 +116,7 @@ Mouse Pos:
}
_contents.Text = stringBuilder.ToString();
MinimumSizeChanged();
// MinimumSizeChanged();
}
protected internal override void Draw(DrawingHandleScreen handle)
@@ -133,10 +137,5 @@ Mouse Pos:
handle.DrawRect(renderBox, Color.Red, false);
}
protected override Vector2 CalculateMinimumSize()
{
return new(175, _contents.CombinedMinimumSize.Y + 10);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Robust.Client.UserInterface.CustomControls
public DebugMemoryPanel()
{
SizeFlagsHorizontal = SizeFlags.None;
HorizontalAlignment = HAlignment.Left;
AddChild(_label = new Label());

View File

@@ -69,12 +69,12 @@ namespace Robust.Client.UserInterface.CustomControls
AddChild(_debugClydePanel = new DebugClydePanel
{
SizeFlagsHorizontal = SizeFlags.None
HorizontalAlignment = HAlignment.Left
});
AddChild(_debugInputPanel = new DebugInputPanel
{
SizeFlagsHorizontal = SizeFlags.None
HorizontalAlignment = HAlignment.Left
});
}
}

View File

@@ -26,7 +26,7 @@ namespace Robust.Client.UserInterface.CustomControls
_contents = new Label();
SizeFlagsHorizontal = SizeFlags.None;
HorizontalAlignment = HAlignment.Left;
_contents = new Label
{
@@ -62,12 +62,7 @@ namespace Robust.Client.UserInterface.CustomControls
_contents.Text = string.Join('\n', bandwidth);
MinimumSizeChanged();
}
protected override Vector2 CalculateMinimumSize()
{
return new(_contents.CombinedMinimumSize.X + 10, _contents.CombinedMinimumSize.Y + 10);
// MinimumSizeChanged();
}
}
}

View File

@@ -33,7 +33,7 @@ namespace Robust.Client.UserInterface.CustomControls
contents = new Label();
SizeFlagsHorizontal = SizeFlags.None;
HorizontalAlignment = HAlignment.Left;
contents = new Label
{
@@ -68,7 +68,6 @@ namespace Robust.Client.UserInterface.CustomControls
if (!NetManager.IsConnected)
{
contents.Text = "Not connected to server.";
MinimumSizeChanged();
return;
}
@@ -89,12 +88,7 @@ namespace Robust.Client.UserInterface.CustomControls
DOWN: {receivedBytes / ONE_KIBIBYTE:N} KiB/s, {receivedPackets} pckt/s, {LastReceivedBytes / ONE_KIBIBYTE:N} KiB, {LastReceivedPackets} pckt
PING: {NetManager.ServerChannel?.Ping ?? -1} ms";
MinimumSizeChanged();
}
protected override Vector2 CalculateMinimumSize()
{
return new(contents.CombinedMinimumSize.X + 10, contents.CombinedMinimumSize.Y + 10);
// MinimumSizeChanged();
}
}
}

View File

@@ -33,7 +33,7 @@ namespace Robust.Client.UserInterface.CustomControls
MouseFilter = _contents.MouseFilter = MouseFilterMode.Ignore;
SizeFlagsHorizontal = SizeFlags.None;
HorizontalAlignment = HAlignment.Left;
}
protected override void Update(FrameEventArgs args)
@@ -52,13 +52,6 @@ namespace Robust.Client.UserInterface.CustomControls
_contents.Text = $@"Paused: {_gameTiming.Paused}, CurTick: {_gameTiming.CurTick}/{_gameTiming.CurTick-1}, CurServerTick: {_gameState.CurServerTick}, Pred: {_gameTiming.CurTick.Value - _gameState.CurServerTick.Value-1}
CurTime: {_gameTiming.CurTime:hh\:mm\:ss\.ff}, RealTime: {_gameTiming.RealTime:hh\:mm\:ss\.ff}, CurFrame: {_gameTiming.CurFrame}
ServerTime: {_gameTiming.ServerTime}, TickTimingAdjustment: {_gameTiming.TickTimingAdjustment}";
MinimumSizeChanged();
}
protected override Vector2 CalculateMinimumSize()
{
return new(_contents.CombinedMinimumSize.X + 10, _contents.CombinedMinimumSize.Y + 10);
}
}
}

View File

@@ -27,11 +27,13 @@ namespace Robust.Client.UserInterface.CustomControls
private OptionButton OverrideMenu;
private Button ClearButton;
private Button EraseButton;
private EntitySpawnButton MeasureButton;
protected override Vector2 ContentsMinimumSize => MainVBox?.CombinedMinimumSize ?? Vector2.Zero;
//protected override Vector2 ContentsMinimumSize => MainVBox?.CombinedMinimumSize ?? Vector2.Zero;
// List of prototypes that are visible based on current filter criteria.
private readonly List<EntityPrototype> _filteredPrototypes = new();
// The indices of the visible prototypes last time UpdateVisiblePrototypes was ran.
// This is inclusive, so end is the index of the last prototype, not right after it.
private (int start, int end) _lastPrototypeIndices;
@@ -55,8 +57,6 @@ namespace Robust.Client.UserInterface.CustomControls
private EntitySpawnButton? SelectedButton;
private EntityPrototype? SelectedPrototype;
protected override Vector2? CustomSize => (250, 300);
public EntitySpawnWindow(IPlacementManager placementManager,
IPrototypeManager prototypeManager,
IResourceCache resourceCache)
@@ -67,8 +67,12 @@ namespace Robust.Client.UserInterface.CustomControls
Title = Loc.GetString("Entity Spawn Panel");
SetSize = (250, 300);
MinSize = (250, 200);
Contents.AddChild(MainVBox = new VBoxContainer
{
Name = "AAAAAA",
Children =
{
new HBoxContainer
@@ -77,7 +81,7 @@ namespace Robust.Client.UserInterface.CustomControls
{
(SearchBar = new LineEdit
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
PlaceHolder = Loc.GetString("Search")
}),
@@ -90,8 +94,8 @@ namespace Robust.Client.UserInterface.CustomControls
},
new ScrollContainer
{
CustomMinimumSize = new Vector2(200.0f, 0.0f),
SizeFlagsVertical = SizeFlags.FillExpand,
MinSize = new Vector2(200.0f, 0.0f),
VerticalExpand = true,
Children =
{
(PrototypeList = new PrototypeListContainer())
@@ -109,15 +113,24 @@ namespace Robust.Client.UserInterface.CustomControls
(OverrideMenu = new OptionButton
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
ToolTip = Loc.GetString("Override placement")
})
}
},
(MeasureButton = new EntitySpawnButton {Visible = false})
new DoNotMeasure
{
Visible = false,
Children =
{
(MeasureButton = new EntitySpawnButton())
}
}
}
});
MeasureButton.Measure(Vector2.Infinity);
for (var i = 0; i < initOpts.Length; i++)
{
OverrideMenu.AddItem(initOpts[i], i);
@@ -225,8 +238,8 @@ namespace Robust.Client.UserInterface.CustomControls
// Update visible buttons in the prototype list.
// Calculate index of first prototype to render based on current scroll.
var height = MeasureButton.CombinedMinimumSize.Y + PrototypeListContainer.Separation;
var offset = -PrototypeList.Position.Y;
var height = MeasureButton.DesiredSize.Y + PrototypeListContainer.Separation;
var offset = Math.Max(-PrototypeList.Position.Y, 0);
var startIndex = (int) Math.Floor(offset / height);
PrototypeList.ItemOffset = startIndex;
@@ -394,7 +407,7 @@ namespace Robust.Client.UserInterface.CustomControls
set
{
_totalItemCount = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -404,13 +417,13 @@ namespace Robust.Client.UserInterface.CustomControls
set
{
_itemOffset = value;
UpdateLayout();
InvalidateMeasure();
}
}
public const float Separation = 2;
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (ChildCount == 0)
{
@@ -419,28 +432,31 @@ namespace Robust.Client.UserInterface.CustomControls
var first = GetChild(0);
var (minX, minY) = first.CombinedMinimumSize;
first.Measure(availableSize);
var (minX, minY) = first.DesiredSize;
return (minX, minY * TotalItemCount + (TotalItemCount - 1) * Separation);
}
protected override void LayoutUpdateOverride()
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
if (ChildCount == 0)
{
return;
return Vector2.Zero;
}
var first = GetChild(0);
var height = first.CombinedMinimumSize.Y;
var height = first.DesiredSize.Y;
var offset = ItemOffset * height + (ItemOffset - 1) * Separation;
foreach (var child in Children)
{
FitChildInBox(child, UIBox2.FromDimensions(0, offset, Width, height));
child.Arrange(UIBox2.FromDimensions(0, offset, Width, height));
offset += Separation + height;
}
return finalSize;
}
}
@@ -458,8 +474,6 @@ namespace Robust.Client.UserInterface.CustomControls
{
AddChild(ActualButton = new Button
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.FillExpand,
ToggleMode = true,
});
@@ -469,16 +483,16 @@ namespace Robust.Client.UserInterface.CustomControls
{
(EntityTextureRects = new LayeredTextureRect
{
CustomMinimumSize = (32, 32),
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
SizeFlagsVertical = SizeFlags.ShrinkCenter,
MinSize = (32, 32),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
Stretch = TextureRect.StretchMode.KeepAspectCentered,
CanShrink = true
}),
(EntityLabel = new Label
{
SizeFlagsVertical = SizeFlags.ShrinkCenter,
SizeFlagsHorizontal = SizeFlags.FillExpand,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
Text = "Backpack",
ClipText = true
})
@@ -497,5 +511,13 @@ namespace Robust.Client.UserInterface.CustomControls
EraseButton.Pressed = false;
}
private class DoNotMeasure : Control
{
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return Vector2.Zero;
}
}
}
}

View File

@@ -39,10 +39,10 @@ namespace Robust.Client.UserInterface.CustomControls
{
_gameTiming = gameTiming;
SizeFlagsHorizontal = SizeFlags.None;
HorizontalAlignment = HAlignment.Left;
}
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return (TrackedFrames * FrameWidth, FrameHeight * 2);
}

View File

@@ -1,15 +1,13 @@
<cc:SS14Window xmlns:cc="clr-namespace:Robust.Client.UserInterface.CustomControls"
xmlns:c="clr-namespace:Robust.Client.UserInterface.Controls">
<c:PanelContainer StyleClasses="windowPanel"/>
<c:VBoxContainer SeparationOverride="0">
<c:PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<c:HBoxContainer>
<c:MarginContainer MarginLeftOverride="5" SizeFlagsHorizontal="FillExpand">
<c:Label Name="TitleLabel" StyleIdentifier="foo" ClipText="True" Text="Exemplary Window Title Here" VAlign="Center" StyleClasses="windowTitle"/>
</c:MarginContainer>
<c:TextureButton Name="CloseButton" StyleClasses="windowCloseButton" SizeFlagsVertical="ShrinkCenter"></c:TextureButton>
</c:HBoxContainer>
</c:PanelContainer>
<c:MarginContainer Name="ContentsContainer" MarginBottomOverride="10" MarginLeftOverride="10" MarginRightOverride="10" MarginTopOverride="10" RectClipContent="True" SizeFlagsVertical="FillExpand" />
</c:VBoxContainer>
</cc:SS14Window>
<SS14Window xmlns="https://spacestation14.io" MinWidth="100" MinHeight="50">
<PanelContainer StyleClasses="windowPanel" />
<VBoxContainer SeparationOverride="0">
<PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<HBoxContainer>
<Label Margin="5 0 0 0" HorizontalExpand="true" Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
Text="{Loc Exemplary Window Title Here}" VAlign="Center" StyleClasses="windowTitle" />
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" VerticalAlignment="Center" />
</HBoxContainer>
</PanelContainer>
<Control Name="ContentsContainer" Margin="10" RectClipContent="True" VerticalExpand="true" />
</VBoxContainer>
</SS14Window>

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Robust.Client.AutoGenerated;
@@ -18,6 +19,7 @@ namespace Robust.Client.UserInterface.CustomControls
public const string StyleClassWindowHeader = "windowHeader";
public const string StyleClassWindowCloseButton = "windowCloseButton";
[Obsolete("Set SetSize instead.")]
protected virtual Vector2? CustomSize => null;
public SS14Window()
@@ -25,7 +27,7 @@ namespace Robust.Client.UserInterface.CustomControls
RobustXamlLoader.Load(this);
MouseFilter = MouseFilterMode.Stop;
WindowHeader.CustomMinimumSize = (0, HEADER_SIZE_Y);
WindowHeader.MinSize = (0, HEADER_SIZE_Y);
Contents = ContentsContainer;
@@ -33,7 +35,7 @@ namespace Robust.Client.UserInterface.CustomControls
XamlChildren = new SS14ContentCollection(this);
}
public MarginContainer Contents { get; private set; }
public Control Contents { get; private set; }
//private TextureButton CloseButton;
private const int DRAG_MARGIN_SIZE = 7;
@@ -42,19 +44,24 @@ namespace Robust.Client.UserInterface.CustomControls
private const float HEADER_SIZE_Y = 25;
protected virtual Vector2 ContentsMinimumSize => (50, 50);
protected override Vector2 CalculateMinimumSize()
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return Vector2.ComponentMax(ContentsMinimumSize, base.CalculateMinimumSize());
return Vector2.ComponentMax(
ContentsMinimumSize,
base.MeasureOverride(Vector2.ComponentMax(availableSize, ContentsMinimumSize)));
}
protected override void Opened()
{
base.Opened();
#pragma warning disable 618
if (_firstTimeOpened && CustomSize != null)
{
LayoutContainer.SetSize(this, CustomSize.Value);
SetSize = CustomSize.Value;
}
#pragma warning restore 618
}
//private Label TitleLabel;

View File

@@ -26,12 +26,9 @@ namespace Robust.Client.UserInterface.CustomControls
},
Children =
{
(OutputPanel = new OutputPanel
{
SizeFlagsVertical = SizeFlags.FillExpand,
})
(OutputPanel = new OutputPanel())
},
SizeFlagsVertical = SizeFlags.FillExpand
VerticalExpand = true,
},
new HBoxContainer
{
@@ -39,7 +36,7 @@ namespace Robust.Client.UserInterface.CustomControls
{
(InputBar = new HistoryLineEdit
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
PlaceHolder = Loc.GetString("Your C# code here.")
}),
(RunButton = new Button {Text = Loc.GetString("Run")})
@@ -50,7 +47,7 @@ namespace Robust.Client.UserInterface.CustomControls
InputBar.OnTextEntered += _ => Run();
RunButton.OnPressed += _ => Run();
CustomMinimumSize = (550, 300);
MinSize = (550, 300);
}
protected abstract void Run();

View File

@@ -25,8 +25,6 @@ namespace Robust.Client.UserInterface.CustomControls
private bool _clearingSelections;
protected override Vector2? CustomSize => (300, 300);
public TileSpawnWindow(ITileDefinitionManager tileDefinitionManager, IPlacementManager placementManager,
IResourceCache resourceCache)
{
@@ -38,7 +36,7 @@ namespace Robust.Client.UserInterface.CustomControls
Contents.AddChild(vBox);
var hBox = new HBoxContainer();
vBox.AddChild(hBox);
SearchBar = new LineEdit {PlaceHolder = "Search", SizeFlagsHorizontal = SizeFlags.FillExpand};
SearchBar = new LineEdit {PlaceHolder = "Search", HorizontalExpand = true};
SearchBar.OnTextChanged += OnSearchBarTextChanged;
hBox.AddChild(SearchBar);
@@ -46,7 +44,7 @@ namespace Robust.Client.UserInterface.CustomControls
ClearButton.OnPressed += OnClearButtonPressed;
hBox.AddChild(ClearButton);
TileList = new ItemList {SizeFlagsVertical = SizeFlags.FillExpand};
TileList = new ItemList {VerticalExpand = true};
TileList.OnItemSelected += TileListOnOnItemSelected;
TileList.OnItemDeselected += TileListOnOnItemDeselected;
vBox.AddChild(TileList);
@@ -57,6 +55,8 @@ namespace Robust.Client.UserInterface.CustomControls
Title = "Place Tiles";
SearchBar.GrabKeyboardFocus();
SetSize = (300, 300);
}
protected override void Dispose(bool disposing)

View File

@@ -45,7 +45,8 @@ namespace Robust.Client.UserInterface
void Render(IRenderHandle renderHandle);
void QueueStyleUpdate(Control control);
void QueueLayoutUpdate(Control control);
void QueueMeasureUpdate(Control control);
void QueueArrangeUpdate(Control control);
void CursorChanged(Control control);
/// <summary>
/// Hides the tooltip for the indicated control, if tooltip for that control is currently showing.

View File

@@ -0,0 +1,23 @@
using JetBrains.Annotations;
using Robust.Shared.Localization;
namespace Robust.Client.UserInterface
{
// TODO: Code a XAML compiler transformer to remove references to this type at compile time.
// And just replace them with the Loc.GetString() call.
[PublicAPI]
public class LocExtension
{
public string Key { get; }
public LocExtension(string key)
{
Key = key;
}
public object ProvideValue()
{
return Loc.GetString(Key);
}
}
}

View File

@@ -39,7 +39,8 @@ namespace Robust.Client.UserInterface
{
LayoutContainer.SetPosition(tooltip, screenPosition);
var combinedMinSize = tooltip.CombinedMinimumSize;
tooltip.Measure(Vector2.Infinity);
var combinedMinSize = tooltip.DesiredSize;
var (right, bottom) = tooltip.Position + combinedMinSize;
if (right > screenBounds.X)

View File

@@ -17,10 +17,11 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface
{
internal sealed class UserInterfaceManager : IDisposable, IUserInterfaceManagerInternal
internal sealed class UserInterfaceManager : IUserInterfaceManagerInternal
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
@@ -34,9 +35,9 @@ namespace Robust.Client.UserInterface
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public UITheme ThemeDefaults { get; private set; } = default!;
[ViewVariables] public UITheme ThemeDefaults { get; private set; } = default!;
public Stylesheet? Stylesheet
[ViewVariables] public Stylesheet? Stylesheet
{
get => _stylesheet;
set
@@ -50,26 +51,28 @@ namespace Robust.Client.UserInterface
}
}
public Control? KeyboardFocused { get; private set; }
[ViewVariables] public Control? KeyboardFocused { get; private set; }
public Control? ControlFocused { get; private set; }
[ViewVariables] public Control? ControlFocused { get; private set; }
public LayoutContainer StateRoot { get; private set; } = default!;
public PopupContainer ModalRoot { get; private set; } = default!;
public Control? CurrentlyHovered { get; private set; } = default!;
public float UIScale { get; private set; } = 1;
public float DefaultUIScale => _displayManager.DefaultWindowScale.X;
public Control RootControl { get; private set; } = default!;
public LayoutContainer WindowRoot { get; private set; } = default!;
public LayoutContainer PopupRoot { get; private set; } = default!;
public DebugConsole DebugConsole { get; private set; } = default!;
public IDebugMonitors DebugMonitors => _debugMonitors;
[ViewVariables] public LayoutContainer StateRoot { get; private set; } = default!;
[ViewVariables] public PopupContainer ModalRoot { get; private set; } = default!;
[ViewVariables] public Control? CurrentlyHovered { get; private set; } = default!;
[ViewVariables] public float UIScale { get; private set; } = 1;
[ViewVariables] public float DefaultUIScale => _displayManager.DefaultWindowScale.X;
[ViewVariables] public Control RootControl { get; private set; } = default!;
[ViewVariables] public LayoutContainer WindowRoot { get; private set; } = default!;
[ViewVariables] public LayoutContainer PopupRoot { get; private set; } = default!;
[ViewVariables] public DebugConsole DebugConsole { get; private set; } = default!;
[ViewVariables] public IDebugMonitors DebugMonitors => _debugMonitors;
private DebugMonitors _debugMonitors = default!;
private readonly List<Control> _modalStack = new();
private bool _rendering = true;
private float _tooltipTimer;
// set to null when not counting down
private float? _tooltipDelay;
private Tooltip _tooltip = default!;
@@ -78,7 +81,8 @@ namespace Robust.Client.UserInterface
private const float TooltipDelay = 1;
private readonly Queue<Control> _styleUpdateQueue = new();
private readonly Queue<Control> _layoutUpdateQueue = new();
private readonly Queue<Control> _measureUpdateQueue = new();
private readonly Queue<Control> _arrangeUpdateQueue = new();
private Stylesheet? _stylesheet;
private ICursor? _worldCursor;
private bool _needUpdateActiveCursor;
@@ -120,9 +124,14 @@ namespace Robust.Client.UserInterface
{
Name = "UIRoot",
MouseFilter = Control.MouseFilterMode.Ignore,
HorizontalAlignment = Control.HAlignment.Stretch,
VerticalAlignment = Control.VAlignment.Stretch,
IsInsideTree = true
};
RootControl.Size = _displayManager.ScreenSize / UIScale;
RootControl.InvalidateMeasure();
QueueMeasureUpdate(RootControl);
_displayManager.OnWindowResized += args => _updateRootSize();
StateRoot = new LayoutContainer
@@ -165,11 +174,6 @@ namespace Robust.Client.UserInterface
_initializeCommon();
}
public void Dispose()
{
RootControl?.Dispose();
}
public void Update(FrameEventArgs args)
{
RootControl.DoUpdate(args);
@@ -192,16 +196,28 @@ namespace Robust.Client.UserInterface
control.DoStyleUpdate();
}
while (_layoutUpdateQueue.Count != 0)
while (_measureUpdateQueue.Count != 0)
{
var control = _layoutUpdateQueue.Dequeue();
var control = _measureUpdateQueue.Dequeue();
if (control.Disposed)
{
continue;
}
control.DoLayoutUpdate();
RunMeasure(control);
}
while (_arrangeUpdateQueue.Count != 0)
{
var control = _arrangeUpdateQueue.Dequeue();
if (control.Disposed)
{
continue;
}
RunArrange(control);
}
// count down tooltip delay if we're not showing one yet and
@@ -222,6 +238,46 @@ namespace Robust.Client.UserInterface
}
}
private void RunMeasure(Control control)
{
if (control.IsMeasureValid || !control.IsInsideTree)
return;
if (control.Parent != null)
{
RunMeasure(control.Parent);
}
if (control == RootControl)
{
control.Measure(_displayManager.ScreenSize / UIScale);
}
else if (control.PreviousMeasure.HasValue)
{
control.Measure(control.PreviousMeasure.Value);
}
}
private void RunArrange(Control control)
{
if (control.IsArrangeValid || !control.IsInsideTree)
return;
if (control.Parent != null)
{
RunArrange(control.Parent);
}
if (control == RootControl)
{
control.Arrange(UIBox2.FromDimensions(Vector2.Zero, _displayManager.ScreenSize / UIScale));
}
else if (control.PreviousArrange.HasValue)
{
control.Arrange(control.PreviousArrange.Value);
}
}
public bool HandleCanFocusDown(Vector2 pointerPosition)
{
var control = MouseGetControl(pointerPosition);
@@ -254,6 +310,7 @@ namespace Robust.Client.UserInterface
{
return false;
}
ControlFocused?.ControlFocusExited();
ControlFocused = control;
@@ -441,6 +498,7 @@ namespace Robust.Client.UserInterface
}
public Vector2 MousePositionScaled => ScreenToUIPosition(_inputManager.MouseScreenPosition);
public Vector2 ScreenToUIPosition(Vector2 position)
{
return position / UIScale;
@@ -556,9 +614,15 @@ namespace Robust.Client.UserInterface
_styleUpdateQueue.Enqueue(control);
}
public void QueueLayoutUpdate(Control control)
public void QueueMeasureUpdate(Control control)
{
_layoutUpdateQueue.Enqueue(control);
_measureUpdateQueue.Enqueue(control);
_arrangeUpdateQueue.Enqueue(control);
}
public void QueueArrangeUpdate(Control control)
{
_arrangeUpdateQueue.Enqueue(control);
}
public void CursorChanged(Control control)
@@ -711,6 +775,7 @@ namespace Robust.Client.UserInterface
PopupRoot.RemoveChild(_suppliedTooltip);
_suppliedTooltip = null;
}
CurrentlyHovered?.PerformHideTooltip();
_resetTooltipTimer();
showingTooltip = false;
@@ -760,7 +825,7 @@ namespace Robust.Client.UserInterface
// show simple tooltip if there is one
_tooltip.Visible = true;
_tooltip.Text = hovered.ToolTip;
Tooltips.PositionTooltip(_tooltip);
Tooltips.PositionTooltip(_tooltip);
}
hovered.PerformShowTooltip();
@@ -791,7 +856,7 @@ namespace Robust.Client.UserInterface
private void _updateRootSize()
{
RootControl.Size = _displayManager.ScreenSize / UIScale;
RootControl.InvalidateMeasure();
}
/// <summary>
@@ -813,6 +878,7 @@ namespace Robust.Client.UserInterface
{
return true;
}
return false;
}
}

View File

@@ -6,13 +6,6 @@ namespace Robust.Client.UserInterface.XAML
{
}
public class XmlnsDefinitionAttribute : Attribute
{
public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace)
{
}
}
public class UsableDuringInitializationAttribute : Attribute
{
public UsableDuringInitializationAttribute(bool usable)

View File

@@ -0,0 +1,16 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Avalonia.Data
{
[UsedImplicitly]
public class Binding
{
public Binding()
{
throw new InvalidOperationException(
"Data binding is not currently supported and this type exists only to make Rider work.");
}
}
}

View File

@@ -0,0 +1,6 @@
using Avalonia.Metadata;
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "Robust.Client.UserInterface.XAML.XNamespace")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface.Controls")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface.CustomControls")]

View File

@@ -0,0 +1,12 @@
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Null
[UsedImplicitly]
public sealed class NullExtension
{
}
}

View File

@@ -0,0 +1,28 @@
# XamlUI notes about Rider
XAML support in both Rider and VS is pretty hardcoded for all supported frameworks. Rider seems less bad about this but they still hardcode a lot of type paths and do other silly stuff. Luckily, they support Avalonia. So... let's pretend to be Avalonia!
**JetBrains, if you're reading this, please don't sue me or take away our licenses pls I only reverse engineered your program to avoid wasting time on your issue tracker.**
## Where to find this info
The primary XAML support in Rider appears to be in `lib/ReSharperHost/JetBrains.ReSharper.Psi.Xaml.dll`. I recommend you decompile it with DotPeek to a project for navigating around. You can then look around the decompiled code (it's not obfuscated at all) to figure out how Rider does all this stuff.
**JetBrains, if you're reading this, please don't sue me or take away our licenses pls I only reverse engineered your program to avoid wasting time on your issue tracker.**
## Attributes
Attributes like `XmlnsAttributionAttribute` are hardcoded for full name with namespace. That's also where it stops, so we just define our own `Avalonia.Metadata.XmlnsDefinitionAttribute` and this fools Rider into recognizing it and working. Hooray!
## `http://schemas.microsoft.com/winfx/2006/xaml` Namespace
As far as I can tell, at least in Avalonia, the things in this namespace are not backed by any real .NET symbols and are just made up by Rider. This is a problem because it only does this making up if it detects that you're using Avalonia.
The heuristic for "are we using Avalonia" appears to be finding an assembly or project reference for `Avalonia.Base`. You can see this in `XamlModulePlatformCache.cs` in the decompiled ReSharper source code and also the `XamlPlatform` flags enum.
For the time being I decided to hold off on actually fully pretending we're Avalonia so did *not* commit to a reference to `Avalonia.Base` yet. Instead we just manually created stuff like a dummy `StaticExtension` class (with a constructor that always throws) and put it into the `http://schemas.microsoft.com/winfx/2006/xaml` namespace. This seems to satisfy Rider without interfering with the actual XamlIL compilation.
## Markup Extensions
Markup extensions have to be classes with a method of signature either `object ProvideValue(IServiceProvider)` or `object ProvideValue()`.
It should be noted that Rider refuses to acknowledge markup extensions unless it finds Avalonia's `Avalonia.Data.Binding` type (or `Avalonia.Markup.Xaml.Data.Binding`). Yes, this is an actual requirement.

View File

@@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Static
[UsedImplicitly]
public sealed class StaticExtension
{
public StaticExtension(string _)
{
throw new InvalidOperationException(
"This type only exists to make Rider work and should never be instantiated.");
}
public static object ProvideValue()
{
throw new InvalidOperationException();
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Type
[UsedImplicitly]
public sealed class TypeExtension
{
public TypeExtension(string _)
{
throw new InvalidOperationException(
"This type only exists to make Rider work and should never be instantiated.");
}
public static object ProvideValue()
{
throw new InvalidOperationException();
}
}
}

Some files were not shown because too many files have changed in this diff Show More