Compare commits

...

67 Commits

Author SHA1 Message Date
Acruid
06e62b031a SoundSystem (#1604)
* Adds the SoundSystem static proxy class for the AudioSystem.
Added a shared IAudioSystem interface for the future.

* Moved ConnectedClient property from IPlayerSession down to ICommonSession.

* Connected up the SoundSystem to the client/server AudioSystems.

* Converted client calls over to the new system.

* Marked the old serverside functions to play sound obsolete, use the new ones from the IAudioSystem.

* Added ISharedPlayerManager to the IoC registration.
2021-03-01 20:22:28 -08:00
Acruid
24707b7385 Shared Containers (#1579)
* Added a basic server simulation framework for help with tests.

* Moved as much as possible to Robust.Shared/Containers.
Moved ContainerSlot from content to engine.

* Moved ClientContainer to shared.

* Merged client/server ContainerManagerComponents into a single shared version.

* ContainerManagerComponent is now implicitly registered with the attributes.

* Migrated to 2021 serialization technology.

* Existing Unit Tests work.

* More tests coverage.
Fixed bug with transferring items between containers.

* Container Type info is now sent over the network.

* Merge client/server container systems.

* Code cleanup.

* Attempted to fix dictionary serialization.
Logs warning when trying to check if an unknown GridId is paused.

* Remove OldCode.
2021-03-01 15:19:59 -08:00
Pieter-Jan Briers
ab95f39f9f Localize SS14Window 2021-03-01 00:45:36 +01:00
Pieter-Jan Briers
cdd38abab5 Fix two shutdown crashes by removing IDisposable managers. 2021-02-28 23:10:03 +01:00
Pieter-Jan Briers
d751c0b3ab Revert "Physics (#1602)"
This reverts commit fefcc7cba3.
2021-02-28 18:45:18 +01:00
Pieter-Jan Briers
2ace0e9e5a Expose Patreon tier info from auth server. 2021-02-28 18:45:01 +01:00
Pieter-Jan Briers
31716f5104 Work around Roslyn scripting bug with ref structs. 2021-02-28 18:45:01 +01:00
metalgearsloth
fefcc7cba3 Physics (#1602)
* Physics worlds

* Paul's a good boy

* Build working

* Ingame and not lagging to hell

* Why didn't you commit ahhhhh

* Hard collisions working

* Solver parity

* Decent broadphase work done

* BroadPhase outline done

* BroadPhase working

* waiting for pvs

* Fix static PVS AABB

* Stop static bodies from awakening

* Optimise a bunch of stuff

* Even more broadphase stuff

* I'm fucking stupid

* Optimise fixture updates

* Collision solver start

* Building

* A is for Argumentative

* Fix contact caching island flags

* Circle shapes actually workeded

* Damping

* DS2 consumables only

* Slightly more stable

* Even slightlier more stablier

* VV your heart out

* Initial joint support

* 90% of joints I just wanted to push as I'd scream if I lost progress

* JOINT PURGATORY

* Joints barely functional lmao

* Okay these joints slightly more functional

* Remove station FrictionJoint

* Also that

* Some Box2D ports

* Cleanup mass

* Edge shape

* Active contacts

* Fix active contacts

* Optimise active contacts even more

* Boxes be stacking

* I would die for smug oh my fucking god

* In which everything is fixed

* Distance joints working LETS GO

* Remove frequency on distancejoint

* Fix some stuff and break joints

* Crashing fixed mehbeh

* ICollideSpecial and more resilience

* auto-clear

* showbb vera

* Slap that TODO in there

* Fix restartround crash

* Random fixes

* Fix fixture networking

* Add intersection method for broadphase

* Fix contacts

* Licenses done

* Optimisations

* Fix wall clips

* Config caching for island

* allocations optimisations

* Optimise casts

* Optimise events queue for physics

* Contact manager optimisations

* Optimise controllers

* Sloth joint or something idk

* Controller graph

* Remove content cvar

* Random cleanup

* Finally remove VirtualController

* Manifold structs again

* Optimise this absolute retardation

* Optimise

* fix license

* Cleanup physics interface

* AHHHHHHHHHHHHH

* Fix collisions again

* snivybus

* Fix potential nasty manifold bug

* Tests go snivy

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-01 03:09:36 +11:00
Paul
30df989e8d Merge branch 'serialization_v3_nodataclasses' of https://github.com/PaulRitter/RobustToolbox into master 2021-02-28 15:55:26 +01:00
Acruid
86bfea6bd4 ICommonSession Improvements + Player Filter (#1600)
* Removed IBaseSession, pushed all members down to ICommonSession.

* Pulled all members of client IPlayerSession into ICommonSession.
Marked client IPlayerSession as obsolete, use the base ICommonSession.

* Restricted setter access for properties in ICommonSession, only engine should be setting them.

* Fixed ping implementation on server.

* Moved AttachedEntityUid to ICommonSession.

* Added a shared IPlayerManager and pulled some common properties down to it.

* Added a shared player Filter class that holds a set of recipients in a networked call. Very useful for selecting recipients in a shared context.
2021-02-27 20:42:54 -08:00
Paul
d890f168c2 Spawner windows remember positions - engine commit 2021-02-27 12:27:46 +01:00
Paul
f888a810bf fixes that pesky warning 2021-02-27 11:58:51 +01:00
tmtmtl30
16249a4dde doubles default gain value (#1593) 2021-02-26 20:54:43 -08:00
Manel Navola
e33488ba55 Implemented erasing rectangular areas (#1419)
* Added support for erasing rectangular areas

* Apply suggestions from code review

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Switched sending start coordinate + end coordinate to sending start coordinate + rect selection size for preventing different parented positions, general code improvements

* Rewritten certain code part so the checks pass

* Added unshaded shader to rect erasing

* Tweaked alpha of erasing rectangle for better visualizing

Co-authored-by: Manel Navola <ManelNavola@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2021-02-26 20:52:12 -08:00
RemieRichards
bfe6eeddb1 Localization Fixes. Stop double-localizing localizationID-sourced names, PROPER returning "True" or "False" instead of "true" or "false" 2021-02-27 01:04:04 +00:00
RemieRichards
7f540e741c Add myself to CODEOWNERS for fluent translations. 2021-02-25 20:30:57 +00:00
Pieter-Jan Briers
b7855f5af5 Fix reloading localizations. 2021-02-25 20:47:17 +01:00
Pieter-Jan Briers
91391e1205 Update NetSerializer submodule 2021-02-25 12:06:28 +01:00
Pieter-Jan Briers
d5199ec459 Update NuGet packages. 2021-02-25 12:06:05 +01:00
Vera Aguilera Puerto
e1e6f4fd10 ContainerHelpers EmptyContainer now has an argument to attach removed entities to grid or map 2021-02-25 11:43:09 +01:00
Leo
e5b6fccf67 Add a scroll speed property to ScrollContainer (#1590)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-02-25 10:39:53 +00:00
RemberBL
95a912c329 Adds args.Handle(); into UI code for scrolling (#1595) 2021-02-25 11:08:19 +01:00
Pieter-Jan Briers
2b4833fc4e Allow content to read assembly versions in sandbox. 2021-02-24 12:18:44 +01:00
Pieter-Jan Briers
b814fc851a Fix more scrollbar DPI scaling bugs. 2021-02-24 12:18:29 +01:00
Pieter-Jan Briers
e87863203b Use DataFieldCached for AppearanceComponent.
What could go wrong?
2021-02-23 23:56:41 +01:00
Pieter-Jan Briers
33b66d9e18 Fix OpenCentered and OpenToLeft window methods. 2021-02-23 23:24:58 +01:00
Pieter-Jan Briers
fd406f7897 Selector-based VV windows have correct size.
Fixes #1594.
2021-02-23 23:10:58 +01:00
Pieter-Jan Briers
7a836d1018 Work around broken nullability.
Revert "Fix nullability errors"

This reverts commit a7f31f9ebf.

Revert "NotNullWhen()"

This reverts commit b332644d48.

Work around broken nullability.
2021-02-23 23:07:19 +01:00
Alex Evgrashin
393c15c44a Post shader will use real sprite bounding box (#1536)
Co-authored-by: Alex Evgrashin <evgrashin.adl@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-02-23 22:54:48 +01:00
Pieter-Jan Briers
a7f31f9ebf Fix nullability errors 2021-02-23 22:53:38 +01:00
RemieRichards
b332644d48 NotNullWhen() 2021-02-23 21:40:33 +00:00
RemieRichards
510f7c0e7c Merge branch 'master' of https://github.com/space-wizards/RobustToolbox into localization_grammar 2021-02-23 21:33:06 +00:00
RemieRichards
fdd05e3d3a Fix GrammarComponent gender parsing, Add tests for GENDER() function (which covers custom types (Enum) and custom functions (GENDER)) 2021-02-23 21:31:13 +00:00
Pieter-Jan Briers
6d41958853 Fix nullability of TryIndex<T>. 2021-02-23 22:25:48 +01:00
Pieter-Jan Briers
cecc4dfcf2 Improve SharedTransformSystem:
Do not fire events for deleted entities.
Optimize to remove allocations & LINQ.
2021-02-23 22:05:49 +01:00
Pieter-Jan Briers
4ac40f2e90 Make norot on sprites default for the time being.
To band aid the pulling issues.
2021-02-23 21:32:00 +01:00
Pieter-Jan Briers
3e12d44173 Bool/enum/entity handling for localization parameters. 2021-02-23 20:59:21 +01:00
RemieRichards
a42b39bd84 Adds GENDER(), PROPER() and ATTRIB() localization functions, GrammarComponent. 2021-02-23 19:53:56 +00:00
DrSmugleaf
22affccf24 Add individual layer offset (#1583)
* Add individual layer offset

* Fix error message

* Bring back layer offsetting
2021-02-23 12:55:45 +01:00
Pieter-Jan Briers
028724c47b Localization improvements:
*Allow content to define localization functions.
* Add rldloc command to reload localizations.
* Doc comments
* Error handling
* Parallelize loading of localization files, since I can only assume we'll have a lot eventually.
* Type system stuff to allow content to pass custom data types into fluent.
* Code cleanup.
2021-02-23 11:35:54 +01:00
Pieter-Jan Briers
0114bff2fc Add IFormattable to sandbox whitelist. 2021-02-23 11:27:51 +01:00
Pieter-Jan Briers
4ddbd644eb Add helper method to set up logging inside unit tests. 2021-02-23 11:27:42 +01:00
Pieter-Jan Briers
f0366531ef Inject the csi directly into my master. 2021-02-23 01:39:33 +01:00
Pieter-Jan Briers
6bd5814f4a Automatically fetch names and descs from loc. 2021-02-22 11:11:11 +01:00
Pieter-Jan Briers
78c132fdab Mute loc warnings for the time being. 2021-02-22 08:57:30 +01:00
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
276 changed files with 7139 additions and 4163 deletions

17
.github/CODEOWNERS vendored
View File

@@ -1,14 +1,7 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
# Last match in file takes precedence.
# These owners will be the default owners for everything in the repo.
# * @defunkt
* @Acruid @PJB3005 @Silvertorch5
# Be they Fluent translations or Freemarker templates, I know them both!
*.ftl @RemieRichards
# Order is important. The last matching pattern has the most precedence.
# So if a pull request only touches javascript files, only these owners
# will be requested to review.
# *.js @octocat @github/js
# You can also use email addresses if you prefer.
# docs/* docs@example.com
# Ping for all PRs
* @Acruid @PJB3005 @Silvertorch5

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 @@
ss14window-placeholder-title = Exemplary Window Title Here

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

@@ -29,9 +29,9 @@ namespace Robust.Build.Tasks
public static Parser<char, float[]> Thickness { get; }
= SkipWhitespaces.Then(
OneOf(
Try(Single1.Select(c => new[] {c})),
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4})),
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4}))
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

@@ -13,6 +13,10 @@ namespace Robust.Build.Tasks
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)
{
@@ -23,6 +27,7 @@ namespace Robust.Build.Tasks
(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)
{
@@ -31,6 +36,12 @@ namespace Robust.Build.Tasks
return (type, ctor);
}
Color = cfg.TypeSystem.GetType("Robust.Shared.Maths.Color");
ColorFromXaml = Color.GetMethod(new FindMethodMethodSignature("FromXaml", Color, XamlIlTypes.String)
{
IsStatic = true
});
}
}

View File

@@ -329,6 +329,77 @@ namespace Robust.Build.Tasks
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;
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Robust.Client.Animations
{
@@ -36,8 +37,7 @@ namespace Robust.Client.Animations
var keyFrame = KeyFrames[keyFrameIndex];
EntitySystem.Get<AudioSystem>()
.Play(keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
SoundSystem.Play(Filter.Local(), keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
}
return (keyFrameIndex, playingTime);

View File

@@ -63,9 +63,11 @@ namespace Robust.Client.Audio.Midi
bool IsAvailable { get; }
public int OcclusionCollisionMask { get; set; }
void Shutdown();
}
internal class MidiManager : IDisposable, IMidiManager
internal class MidiManager : IMidiManager
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
@@ -352,7 +354,7 @@ namespace Robust.Client.Audio.Midi
}
}
public void Dispose()
public void Shutdown()
{
_alive = false;
_midiThread?.Join();

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,11 +18,13 @@ 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;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -33,7 +36,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>();
@@ -51,6 +54,7 @@ namespace Robust.Client
IoCManager.Register<IClientGameStateManager, ClientGameStateManager>();
IoCManager.Register<IBaseClient, BaseClient>();
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
IoCManager.Register<IStateManager, StateManager>();
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
@@ -59,6 +63,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

@@ -0,0 +1,20 @@
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Client.Console.Commands
{
internal sealed class ReloadLocalizationsCommand : IConsoleCommand
{
public string Command => "rldloc";
public string Description => "Reloads localization (client & server)";
public string Help => "Usage: rldloc";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
IoCManager.Resolve<ILocalizationManager>().ReloadLocalizations();
shell.RemoteExecuteCommand("sudo rldloc");
}
}
}

View File

@@ -1,5 +1,8 @@
#if CLIENT_SCRIPTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
@@ -36,6 +39,8 @@ namespace Robust.Client.Console
private readonly ScriptGlobals _globals;
private ScriptState? _state;
private (string[] imports, string code)? _autoImportRepeatBuffer;
public ScriptConsoleClient()
{
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
@@ -54,38 +59,56 @@ namespace Robust.Client.Console
var code = InputBar.Text;
InputBar.Clear();
// Remove > or . at the end of the output panel.
OutputPanel.RemoveEntry(^1);
_inputBuffer.AppendLine(code);
_linesEntered += 1;
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()), ScriptInstanceShared.ParseOptions);
if (!SyntaxFactory.IsCompleteSubmission(tree))
if (_autoImportRepeatBuffer.HasValue && code == "y")
{
if (_linesEntered == 1)
var (imports, repeatCode) = _autoImportRepeatBuffer.Value;
var sb = new StringBuilder();
foreach (var import in imports)
{
OutputPanel.AddText($"> {code}");
sb.AppendFormat("using {0};\n", import);
}
else
{
OutputPanel.AddText($". {code}");
}
OutputPanel.AddText(".");
return;
sb.Append(repeatCode);
code = sb.ToString();
}
code = _inputBuffer.ToString().Trim();
// Remove echo of partial submission from the output panel.
for (var i = 1; i < _linesEntered; i++)
else
{
// Remove > or . at the end of the output panel.
OutputPanel.RemoveEntry(^1);
}
_inputBuffer.Clear();
_linesEntered = 0;
_inputBuffer.AppendLine(code);
_linesEntered += 1;
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()),
ScriptInstanceShared.ParseOptions);
if (!SyntaxFactory.IsCompleteSubmission(tree))
{
if (_linesEntered == 1)
{
OutputPanel.AddText($"> {code}");
}
else
{
OutputPanel.AddText($". {code}");
}
OutputPanel.AddText(".");
return;
}
code = _inputBuffer.ToString().Trim();
// Remove echo of partial submission from the output panel.
for (var i = 1; i < _linesEntered; i++)
{
OutputPanel.RemoveEntry(^1);
}
_inputBuffer.Clear();
_linesEntered = 0;
}
Script newScript;
@@ -135,6 +158,8 @@ namespace Robust.Client.Console
OutputPanel.AddMessage(msg);
OutputPanel.AddText(">");
PromptAutoImports(e.Diagnostics, code);
return;
}
@@ -148,13 +173,23 @@ namespace Robust.Client.Console
else if (ScriptInstanceShared.HasReturnValue(newScript))
{
var msg = new FormattedMessage();
msg.AddText(CSharpObjectFormatter.Instance.FormatObject(_state.ReturnValue));
msg.AddText(ScriptInstanceShared.SafeFormat(_state.ReturnValue));
OutputPanel.AddMessage(msg);
}
OutputPanel.AddText(">");
}
private void PromptAutoImports(IEnumerable<Diagnostic> diags, string code)
{
if (!ScriptInstanceShared.CalcAutoImports(_reflectionManager, diags, out var found))
return;
OutputPanel.AddText($"Auto-import {string.Join(", ", found)} (enter 'y')?");
_autoImportRepeatBuffer = (found.ToArray(), code);
}
private sealed class ScriptGlobalsImpl : ScriptGlobals
{
private readonly ScriptConsoleClient _owner;
@@ -180,7 +215,7 @@ namespace Robust.Client.Console
public override void show(object obj)
{
write(CSharpObjectFormatter.Instance.FormatObject(obj));
write(ScriptInstanceShared.SafeFormat(obj));
}
}
}

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

@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Robust.Client.Audio.Midi;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
@@ -26,7 +27,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;
@@ -62,6 +62,7 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
[Dependency] private readonly IAuthManager _authManager = default!;
[Dependency] private readonly IMidiManager _midiManager = default!;
private CommandLineArgs? _commandLineArgs;
private bool _disableAssemblyLoadContext;
@@ -163,6 +164,7 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
_mapManager.Initialize();
@@ -318,6 +320,7 @@ namespace Robust.Client
logManager.GetSawmill("discord").Level = LogLevel.Warning;
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
logManager.GetSawmill("szr").Level = LogLevel.Info;
// logManager.GetSawmill("loc").Level = LogLevel.Error;
#if DEBUG_ONLY_FCE_INFO
#if DEBUG_ONLY_FCE_LOG
@@ -376,6 +379,8 @@ namespace Robust.Client
private void Cleanup()
{
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
namespace Robust.Client.GameObjects
@@ -25,7 +26,6 @@ namespace Robust.Client.GameObjects
RegisterReference<PhysicsComponent, IPhysBody>();
RegisterReference<PhysicsComponent, IPhysicsComponent>();
RegisterIgnore("KeyBindingInput");
Register<PointLightComponent>();
Register<InputComponent>();
@@ -50,9 +50,6 @@ namespace Robust.Client.GameObjects
Register<AnimationPlayerComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<TimerComponent>();
#if DEBUG

View File

@@ -116,7 +116,7 @@ namespace Robust.Client.GameObjects
_didRegisterSerializer = true;
}
serializer.DataField(ref Visualizers, "visuals", new List<AppearanceVisualizer>());
serializer.DataFieldCached(ref Visualizers, "visuals", new List<AppearanceVisualizer>());
}
public override void Initialize()

View File

@@ -1,86 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent
{
[DebuggerDisplay("ClientContainer {Owner.Uid}/{ID}")]
private sealed class ClientContainer : IContainer
{
public List<IEntity> Entities { get; } = new List<IEntity>();
public ClientContainer(string id, ContainerManagerComponent manager)
{
ID = id;
Manager = manager;
}
[ViewVariables] public IContainerManager Manager { get; }
[ViewVariables] public string ID { get; }
[ViewVariables] public IEntity Owner => Manager.Owner;
[ViewVariables] public bool Deleted { get; private set; }
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => Entities;
[ViewVariables]
public bool ShowContents { get; set; }
[ViewVariables]
public bool OccludesLight { get; set; }
public bool CanInsert(IEntity toinsert)
{
return false;
}
public bool Insert(IEntity toinsert)
{
return false;
}
public bool CanRemove(IEntity toremove)
{
return false;
}
public bool Remove(IEntity toremove)
{
return false;
}
public void ForceRemove(IEntity toRemove)
{
throw new NotSupportedException("Cannot directly modify containers on the client");
}
public bool Contains(IEntity contained)
{
return Entities.Contains(contained);
}
public void DoInsert(IEntity entity)
{
Entities.Add(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void DoRemove(IEntity entity)
{
Entities.Remove(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void Shutdown()
{
Deleted = true;
}
}
public override void InternalContainerShutdown(IContainer container)
{
}
}
}

View File

@@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent : SharedContainerManagerComponent
{
[ViewVariables]
private readonly Dictionary<string, ClientContainer> _containers = new();
public override T MakeContainer<T>(string id)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override bool Remove(IEntity entity)
{
// TODO: This will probably need relaxing if we want to predict things like inventories.
throw new NotSupportedException("Cannot modify containers on the client.");
}
protected override IEnumerable<IContainer> GetAllContainersImpl()
{
return _containers.Values.Where(c => !c.Deleted);
}
public override IContainer GetContainer(string id)
{
return _containers[id];
}
public override bool HasContainer(string id)
{
return _containers.ContainsKey(id);
}
public override bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
var ret = _containers.TryGetValue(id, out var cont);
container = cont!;
return ret;
}
/// <inheritdoc />
public override bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container)
{
foreach (var contain in _containers.Values)
{
if (!contain.Deleted && contain.Contains(entity))
{
container = contain;
return true;
}
}
container = default;
return false;
}
public override bool ContainsEntity(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (!container.Deleted && container.Contains(entity))
{
return true;
}
}
return false;
}
public override void ForceRemove(IEntity entity)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if(!(curState is ContainerManagerComponentState cast))
return;
// Delete now-gone containers.
List<string>? toDelete = null;
foreach (var (id, container) in _containers)
{
if (!cast.Containers.ContainsKey(id))
{
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);
}
}
if (toDelete != null)
{
foreach (var dead in toDelete)
{
_containers.Remove(dead);
}
}
// Add new containers and update existing contents.
foreach (var (id, data) in cast.Containers)
{
if (!_containers.TryGetValue(id, out var container))
{
container = new ClientContainer(id, this);
_containers.Add(id, container);
}
// sync show flag
container.ShowContents = data.ShowContents;
container.OccludesLight = data.OccludesLight;
// Remove gone entities.
List<IEntity>? toRemove = null;
foreach (var entity in container.Entities)
{
if (!data.ContainedEntities.Contains(entity.Uid))
{
toRemove ??= new List<IEntity>();
toRemove.Add(entity);
}
}
if (toRemove != null)
{
foreach (var goner in toRemove)
{
container.DoRemove(goner);
}
}
// Add new entities.
foreach (var uid in data.ContainedEntities)
{
var entity = Owner.EntityManager.GetEntity(uid);
if (!container.Entities.Contains(entity))
{
container.DoInsert(entity);
}
}
}
}
protected override void Shutdown()
{
base.Shutdown();
// On shutdown we won't get to process remove events in the containers so this has to be manually done.
foreach (var container in _containers.Values)
{
foreach (var containerEntity in container.Entities)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new UpdateContainerOcclusionMessage(containerEntity));
}
}
}
}
}

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,12 @@ namespace Robust.Client.GameObjects
ISpriteLayer this[object layerKey] { get; }
IEnumerable<ISpriteLayer> AllLayers { get; }
int GetLayerDirectionCount(ISpriteLayer layer);
/// <summary>
/// Calculate sprite bounding box in world-space coordinates.
/// </summary>
Box2 CalculateBoundingBox();
}
}
}

View File

@@ -26,5 +26,17 @@ namespace Robust.Client.GameObjects
RSI.State.Direction EffectiveDirection(Angle worldRotation);
Vector2 LocalToLayer(Vector2 localPos);
/// <summary>
/// Layer size in pixels.
/// Don't account layer scale or sprite world transform.
/// </summary>
Vector2i PixelSize { get; }
/// <summary>
/// Calculate layer bounding box in sprite local-space coordinates.
/// </summary>
/// <returns>Bounding box in sprite local-space coordinates.</returns>
Box2 CalculateBoundingBox();
}
}

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)
{
@@ -936,6 +945,31 @@ namespace Robust.Client.GameObjects
LayerSetAutoAnimated(layer, autoAnimated);
}
public void LayerSetOffset(int layer, Vector2 layerOffset)
{
if (Layers.Count <= layer)
{
Logger.ErrorS(LogCategory,
"Layer with index '{0}' does not exist, cannot set offset! Trace:\n{1}",
layer, Environment.StackTrace);
return;
}
Layers[layer].SetOffset(layerOffset);
}
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
{
if (!LayerMapTryGet(layerKey, out var layer))
{
Logger.ErrorS(LogCategory, "Layer with key '{0}' does not exist, cannot set offset! Trace:\n{1}",
layerKey, Environment.StackTrace);
return;
}
LayerSetOffset(layer, layerOffset);
}
/// <inheritdoc />
public RSI.StateId LayerGetState(int layer)
{
@@ -965,40 +999,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 +1061,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) + layer.Offset;
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 +1163,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", true);
serializer.DataFieldCached(ref _enableOverrideDirection, "enableOverrideDir", false);
serializer.DataFieldCached(ref _overrideDirection, "overrideDir", Direction.East);
// TODO: Writing?
if (!serializer.Reading)
@@ -1314,7 +1422,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 +1478,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()
@@ -1485,6 +1604,39 @@ namespace Robust.Client.GameObjects
return builder.ToString();
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox()
{
// fast check for empty sprites
if (Layers.Count == 0)
return new Box2();
// we need to calculate bounding box taking into account all nested layers
// because layers can have offsets, scale or rotation we need to calculate a new BB
// based on lowest bottomLeft and hightest topRight points from all layers
var box = Layers[0].CalculateBoundingBox();
for (int i = 1; i < Layers.Count; i++)
{
var layer = Layers[i];
var layerBB = layer.CalculateBoundingBox();
box = box.Union(layerBB);
}
// apply sprite transformations and calculate sprite bounding box
// we can optimize it a bit, if sprite doesn't have rotation
var spriteBox = box.Scale(Scale);
var spriteHasRotation = !Rotation.EqualsApprox(Angle.Zero);
var spriteBB = spriteHasRotation ?
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
// move it all to world transform system (with sprite offset)
var worldPosition = Owner.Transform.WorldPosition;
var worldBB = spriteBB.Translated(Offset + worldPosition);
return worldBB;
}
/// <summary>
/// Enum to "offset" a cardinal direction.
/// </summary>
@@ -1526,16 +1678,25 @@ namespace Robust.Client.GameObjects
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Scale { get; set; } = Vector2.One;
[ViewVariables(VVAccess.ReadWrite)]
public Angle Rotation { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible = true;
[ViewVariables(VVAccess.ReadWrite)]
public Color Color { get; set; } = Color.White;
[ViewVariables(VVAccess.ReadWrite)]
public bool AutoAnimated = true;
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Offset { get; set; }
[ViewVariables]
public DirectionOffset DirOffset { get; set; }
[ViewVariables]
public RSI? ActualRsi => RSI ?? _parent.BaseRSI;
@@ -1756,6 +1917,38 @@ namespace Robust.Client.GameObjects
_parent.UpdateIsInert();
}
public void SetOffset(Vector2 offset)
{
Offset = offset;
}
/// <inheritdoc/>
public Vector2i PixelSize
{
get
{
var pixelSize = Vector2i.Zero;
if (Texture != null)
{
pixelSize = Texture.Size;
}
else if (ActualRsi != null)
{
pixelSize = ActualRsi.Size;
}
return pixelSize;
}
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox()
{
// TODO: scale & rotation for layers is currently unimplemented.
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
}
}
void IAnimationProperties.SetAnimatableProperty(string name, object value)

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Audio;
@@ -11,12 +10,13 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public class AudioSystem : EntitySystem
public class AudioSystem : EntitySystem, IAudioSystem
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -25,9 +25,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
public int OcclusionCollisionMask;
/// <inheritdoc />
public override void Initialize()
{
@@ -35,6 +33,8 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<PlayAudioGlobalMessage>(PlayAudioGlobalHandler);
SubscribeNetworkEvent<PlayAudioPositionalMessage>(PlayAudioPositionalHandler);
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
@@ -176,7 +176,6 @@ namespace Robust.Client.GameObjects
{
stream.Source.Dispose();
stream.Done = true;
stream.DoPlaybackDone();
}
/// <summary>
@@ -335,92 +334,30 @@ namespace Robust.Client.GameObjects
{
Source.StopPlaying();
}
public event Action? PlaybackDone;
public void DoPlaybackDone()
{
PlaybackDone?.Invoke();
}
}
}
public interface IPlayingAudioStream
{
void Stop();
event Action PlaybackDone;
}
public static class AudioSystemExtensions
{
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
string filename,
AudioParams? audioParams,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, entity, audioParams);
}
/// <summary>
/// Play an audio stream following an entity.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public int DefaultSoundRange => 25;
/// <inheritdoc />
public int OcclusionCollisionMask { get; set; }
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, entity, audioParams);
return Play(filename, audioParams);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
string filename,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, coordinates, audioParams);
return Play(filename, entity, audioParams);
}
/// <summary>
/// Play an audio stream at a static position.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, coordinates, audioParams);
return Play(filename, coordinates, audioParams);
}
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
public class ContainerSystem : EntitySystem
public class ClientContainerSystem : ContainerSystem
{
private readonly HashSet<IEntity> _updateQueue = new();
@@ -91,14 +91,4 @@ namespace Robust.Client.GameObjects
}
}
}
internal readonly struct UpdateContainerOcclusionMessage
{
public UpdateContainerOcclusionMessage(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -36,6 +36,9 @@ namespace Robust.Client.Graphics.Clyde
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _bufferedSourceDisposeQueue = new();
private readonly ConcurrentQueue<int> _bufferDisposeQueue = new();
// The base gain value for a listener, used to boost the default volume.
private const float _baseGain = 2f;
public bool HasAlDeviceExtension(string extension) => _alcDeviceExtensions.Contains(extension);
public bool HasAlContextExtension(string extension) => _alContextExtensions.Contains(extension);
@@ -182,7 +185,7 @@ namespace Robust.Client.Graphics.Clyde
public void SetMasterVolume(float newVolume)
{
AL.Listener(ALListenerf.Gain, newVolume);
AL.Listener(ALListenerf.Gain, _baseGain * newVolume);
}
public IClydeAudioSource CreateAudioSource(AudioStream stream)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -183,25 +183,61 @@ namespace Robust.Client.Graphics.Clyde
break;
}
RenderTexture? entityPostRenderTarget = null;
Vector2i roundedPos = default;
if (entry.sprite.PostShader != null)
{
_renderHandle.UseRenderTarget(EntityPostRenderTarget);
_renderHandle.Clear(new Color());
// Calculate viewport so that the entity thinks it's drawing to the same position,
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
var spritePos = entry.sprite.Owner.Transform.WorldPosition;
var screenPos = _eyeManager.WorldToScreen(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
flippedPos -= EntityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
// calculate world bounding box
var spriteBB = entry.sprite.CalculateBoundingBox();
var spriteLB = spriteBB.BottomLeft;
var spriteRT = spriteBB.TopRight;
// finally we can calculate screen bounding in pixels
var screenLB = _eyeManager.WorldToScreen(spriteLB);
var screenRT = _eyeManager.WorldToScreen(spriteRT);
// we need to scale RT a for effects like emission or highlight
// scale can be passed with PostShader as variable in future
var postShadeScale = 1.25f;
var screenSpriteSize = (Vector2i)((screenRT - screenLB) * postShadeScale).Rounded();
screenSpriteSize.Y = -screenSpriteSize.Y;
// I'm not 100% sure why it works, but without it post-shader
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
// probably some rotation rounding error
if (screenSpriteSize.X % 2 != 0)
screenSpriteSize.X++;
if (screenSpriteSize.Y % 2 != 0)
screenSpriteSize.Y++;
// check that sprite size is valid
if (screenSpriteSize.X > 0 && screenSpriteSize.Y > 0)
{
// create new render texture with correct sprite size
entityPostRenderTarget = CreateRenderTarget(screenSpriteSize,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
name: nameof(entityPostRenderTarget));
_renderHandle.UseRenderTarget(entityPostRenderTarget);
_renderHandle.Clear(new Color());
// Calculate viewport so that the entity thinks it's drawing to the same position,
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
var spritePos = spriteBB.Center;
var screenPos = _eyeManager.WorldToScreen(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i)screenPos;
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
flippedPos -= entityPostRenderTarget.Size / 2;
_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)
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
{
var oldProj = _currentMatrixProj;
var oldView = _currentMatrixView;
@@ -214,11 +250,11 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.SetProjView(proj, view);
_renderHandle.SetModelTransform(Matrix3.Identity);
var rounded = roundedPos - EntityPostRenderTarget.Size / 2;
var rounded = roundedPos - entityPostRenderTarget.Size / 2;
var box = Box2i.FromDimensions(rounded, EntityPostRenderTarget.Size);
var box = Box2i.FromDimensions(rounded, entityPostRenderTarget.Size);
_renderHandle.DrawTextureScreen(EntityPostRenderTarget.Texture,
_renderHandle.DrawTextureScreen(entityPostRenderTarget.Texture,
box.BottomLeft, box.BottomRight, box.TopLeft, box.TopRight,
Color.White, 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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@@ -39,8 +39,6 @@ namespace Robust.Client.Graphics.Clyde
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
private RenderTexture EntityPostRenderTarget = default!;
private GLBuffer BatchVBO = default!;
private GLBuffer BatchEBO = default!;
private GLHandle BatchVAO;
@@ -88,7 +86,7 @@ namespace Robust.Client.Graphics.Clyde
public override bool Initialize()
{
base.Initialize();
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
if (!InitWindowing())
@@ -152,6 +150,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));
@@ -314,10 +314,6 @@ namespace Robust.Client.Graphics.Clyde
ProjViewUBO = new GLUniformBuffer<ProjViewMatrices>(this, BindingIndexProjView, nameof(ProjViewUBO));
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
EntityPostRenderTarget = CreateRenderTarget(Vector2i.One * 8 * EyeManager.PixelsPerMeter,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
name: nameof(EntityPostRenderTarget));
CreateMainViewport();
}

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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.ResourceManagement;
@@ -21,6 +21,7 @@ using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Log;
namespace Robust.Client.Placement
{
@@ -87,7 +88,17 @@ namespace Robust.Client.Placement
public bool Eraser { get; private set; }
/// <summary>
/// The texture we use to show from our placement manager to represent the entity to place
/// Holds the selection rectangle for the eraser
/// </summary>
public Box2? EraserRect { get; set; }
/// <summary>
/// Drawing shader for drawing without being affected by lighting
/// </summary>
private ShaderInstance? _drawingShader { get; set; }
/// <summary>
/// The texture we use to show from our placement manager to represent the entity to place
/// </summary>
public List<IDirectionalTextureProvider>? CurrentTextures { get; set; }
@@ -153,6 +164,8 @@ namespace Robust.Client.Placement
public void Initialize()
{
_drawingShader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
NetworkManager.RegisterNetMessage<MsgPlacement>(MsgPlacement.NAME, HandlePlacementMessage);
_modeDictionary.Clear();
@@ -182,7 +195,17 @@ namespace Robust.Client.Placement
.Bind(EngineKeyFunctions.EditorGridPlace, InputCmdHandler.FromDelegate(
session =>
{
if (IsActive && !Eraser) ActivateGridMode();
if (IsActive)
{
if (Eraser)
{
EraseRectMode();
}
else
{
ActivateGridMode();
}
}
}))
.Bind(EngineKeyFunctions.EditorPlaceObject, new PointerStateInputCmdHandler(
(session, coords, uid) =>
@@ -190,6 +213,13 @@ namespace Robust.Client.Placement
if (!IsActive)
return false;
if (EraserRect.HasValue)
{
HandleRectDeletion(StartPoint, EraserRect.Value);
EraserRect = null;
return true;
}
if (Eraser)
{
if (HandleDeletion(coords))
@@ -308,6 +338,7 @@ namespace Robust.Client.Placement
_placenextframe = false;
IsActive = false;
Eraser = false;
EraserRect = null;
PlacementOffset = Vector2i.Zero;
}
@@ -384,6 +415,15 @@ namespace Robust.Client.Placement
NetworkManager.ClientSendMessage(msg);
}
public void HandleRectDeletion(EntityCoordinates start, Box2 rect)
{
var msg = NetworkManager.CreateNetMessage<MsgPlacement>();
msg.PlaceType = PlacementManagerMessage.RequestRectRemove;
msg.EntityCoordinates = new EntityCoordinates(StartPoint.EntityId, rect.BottomLeft);
msg.RectSize = rect.Size;
NetworkManager.ClientSendMessage(msg);
}
public void ToggleEraser()
{
if (!Eraser && !IsActive)
@@ -459,11 +499,62 @@ namespace Robust.Client.Placement
return true;
}
private bool CurrentEraserMouseCoordinates(out EntityCoordinates coordinates)
{
var ent = PlayerManager.LocalPlayer?.ControlledEntity;
if (ent == null)
{
coordinates = new EntityCoordinates();
return false;
}
else
{
var map = ent.Transform.MapID;
if (map == MapId.Nullspace || !Eraser)
{
coordinates = new EntityCoordinates();
return false;
}
coordinates = EntityCoordinates.FromMap(ent.EntityManager, MapManager,
eyeManager.ScreenToMap(new ScreenCoordinates(_inputManager.MouseScreenPosition)));
return true;
}
}
/// <inheritdoc />
public void FrameUpdate(FrameEventArgs e)
{
if (!CurrentMousePosition(out var mouseScreen))
{
if (EraserRect.HasValue)
{
if (!CurrentEraserMouseCoordinates(out EntityCoordinates end))
return;
float b, l, t, r;
if (StartPoint.X < end.X)
{
l = StartPoint.X;
r = end.X;
}
else
{
l = end.X;
r = StartPoint.X;
}
if (StartPoint.Y < end.Y)
{
b = StartPoint.Y;
t = end.Y;
}
else
{
b = end.Y;
t = StartPoint.Y;
}
EraserRect = new Box2(l, b, r, t);
}
return;
}
CurrentMode!.AlignPlacementMode(mouseScreen);
@@ -501,6 +592,15 @@ namespace Robust.Client.Placement
PlacementType = PlacementTypes.Grid;
}
private void EraseRectMode()
{
if (!CurrentEraserMouseCoordinates(out EntityCoordinates coordinates))
return;
StartPoint = coordinates;
EraserRect = new Box2(coordinates.Position, Vector2.Zero);
}
private bool DeactivateSpecialPlacement()
{
if (PlacementType == PlacementTypes.None)
@@ -513,7 +613,14 @@ namespace Robust.Client.Placement
private void Render(DrawingHandleWorld handle)
{
if (CurrentMode == null || !IsActive)
{
if (EraserRect.HasValue)
{
handle.UseShader(_drawingShader);
handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
}
return;
}
CurrentMode.Render(handle);

View File

@@ -1,13 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
namespace Robust.Client.Player
{
public interface IPlayerManager
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
{
IEnumerable<IPlayerSession> Sessions { get; }
new IEnumerable<IPlayerSession> Sessions { get; }
IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict { get; }
LocalPlayer? LocalPlayer { get; }
@@ -17,8 +17,6 @@ namespace Robust.Client.Player
/// </summary>
event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
int PlayerCount { get; }
int MaxPlayers { get; }
event EventHandler PlayerListUpdated;
void Initialize();

View File

@@ -1,20 +1,11 @@
using Robust.Shared.Players;
using System;
using Robust.Shared.Players;
namespace Robust.Client.Player
{
/// <summary>
/// Client side session of a player.
/// Client side session of a player.
/// </summary>
public interface IPlayerSession : ICommonSession
{
/// <summary>
/// Current name of this player.
/// </summary>
new string Name { get; set; }
/// <summary>
/// Current connection latency of this session from the server to their client.
/// </summary>
short Ping { get; set; }
}
[Obsolete("Use the base " + nameof(ICommonSession))]
public interface IPlayerSession : ICommonSession { }
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Configuration;
@@ -8,6 +8,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -30,6 +31,21 @@ namespace Robust.Client.Player
/// </summary>
private readonly Dictionary<NetUserId, IPlayerSession> _sessions = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions
{
get
{
if (LocalPlayer is not null)
return new[] {LocalPlayer.Session};
return Enumerable.Empty<ICommonSession>();
}
}
/// <inheritdoc />
IEnumerable<ICommonSession> ISharedPlayerManager.Sessions => _sessions.Values;
/// <inheritdoc />
public int PlayerCount => _sessions.Values.Count;
@@ -52,9 +68,9 @@ namespace Robust.Client.Player
private LocalPlayer? _localPlayer;
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
/// <inheritdoc />
[ViewVariables] public IEnumerable<IPlayerSession> Sessions => _sessions.Values;
[ViewVariables]
IEnumerable<IPlayerSession> IPlayerManager.Sessions => _sessions.Values;
/// <inheritdoc />
public IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict => _sessions;
@@ -191,7 +207,7 @@ namespace Robust.Client.Player
if (state.UserId == LocalPlayer!.UserId)
{
LocalPlayer.InternalSession = newSession;
newSession.ConnectedClient = _network.ServerChannel!;
// We just connected to the server, hurray!
LocalPlayer.SwitchState(SessionStatus.Connecting, newSession.Status);
}

View File

@@ -1,25 +1,53 @@
using Robust.Shared.Enums;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
namespace Robust.Client.Player
{
internal sealed class PlayerSession : IPlayerSession
{
/// <inheritdoc />
public SessionStatus Status { get; set; } = SessionStatus.Connecting;
internal SessionStatus Status { get; set; } = SessionStatus.Connecting;
/// <inheritdoc />
SessionStatus ICommonSession.Status
{
get => this.Status;
set => this.Status = value;
}
/// <inheritdoc />
public IEntity? AttachedEntity { get; set; }
/// <inheritdoc />
public EntityUid? AttachedEntityUid => AttachedEntity?.Uid;
/// <inheritdoc />
public NetUserId UserId { get; }
/// <inheritdoc cref="IPlayerSession" />
public string Name { get; set; } = "<Unknown>";
internal string Name { get; set; } = "<Unknown>";
/// <inheritdoc cref="IPlayerSession" />
string ICommonSession.Name
{
get => this.Name;
set => this.Name = value;
}
/// <inheritdoc />
public short Ping { get; set; }
internal short Ping { get; set; }
/// <inheritdoc />
public INetChannel ConnectedClient { get; internal set; } = null!;
/// <inheritdoc />
short ICommonSession.Ping
{
get => this.Ping;
set => this.Ping = value;
}
/// <summary>
/// Creates an instance of a PlayerSession.

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

@@ -11,13 +11,13 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NJsonSchema" Version="10.3.8" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NJsonSchema" Version="10.3.1" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageReference Include="OpenToolkit.OpenAL" Version="4.0.0-pre9.1" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />

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)
@@ -475,6 +473,8 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar.ValueTarget -= _getScrollSpeed() * args.Delta.Y;
_isAtBottom = _scrollBar.IsAtEnd;
args.Handle();
}
[Pure]

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

@@ -60,14 +60,14 @@ namespace Robust.Client.UserInterface.Controls
_label = new Label
{
StyleClasses = { StyleClassOptionButton },
SizeFlagsHorizontal = SizeFlags.FillExpand,
HorizontalExpand = true,
};
hBox.AddChild(_label);
_triangle = new TextureRect
{
StyleClasses = { StyleClassOptionTriangle },
SizeFlagsVertical = SizeFlags.ShrinkCenter,
VerticalAlignment = VAlignment.Center,
Visible = !HideTriangle
};
hBox.AddChild(_triangle);
@@ -118,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);

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

@@ -101,12 +101,12 @@ namespace Robust.Client.UserInterface.Controls
}
var box = _getGrabberBox();
if (!box.Contains(args.RelativePosition))
if (!box.Contains(args.RelativePixelPosition))
{
return;
}
_grabData = (args.RelativePosition, Value);
_grabData = (args.RelativePixelPosition, Value);
_updatePseudoClass();
args.Handle();
}
@@ -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

@@ -17,6 +17,9 @@ namespace Robust.Client.UserInterface.Controls
private bool _suppressScrollValueChanged;
public int ScrollSpeedX { get; set; } = 50;
public int ScrollSpeedY { get; set; } = 50;
public ScrollContainer()
{
MouseFilter = MouseFilterMode.Pass;
@@ -26,14 +29,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 +50,7 @@ namespace Robust.Client.UserInterface.Controls
set
{
_vScrollEnabled = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -57,17 +60,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 +113,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 +148,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 +165,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 +187,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)
@@ -191,13 +200,15 @@ namespace Robust.Client.UserInterface.Controls
if (_vScrollEnabled)
{
_vScrollBar.ValueTarget -= args.Delta.Y * 50;
_vScrollBar.ValueTarget -= args.Delta.Y * ScrollSpeedY;
}
if (_hScrollEnabled)
{
_hScrollBar.ValueTarget += args.Delta.X * 50;
_hScrollBar.ValueTarget += args.Delta.X * ScrollSpeedX;
}
args.Handle();
}
protected override void ChildAdded(Control newChild)
@@ -223,10 +234,12 @@ namespace Robust.Client.UserInterface.Controls
{
h = 0;
}
if (!_vScrollVisible)
{
v = 0;
}
return new Vector2(h, v);
}
@@ -237,7 +250,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,12 +222,13 @@ 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.
// But we know that this is always going to be one of the roots anyway for now.
LayoutContainer.SetPosition(this, (UserInterfaceManager.RootControl.Size - Size) / 2);
LayoutContainer.SetPosition(this, (UserInterfaceManager.RootControl.Size - SetSize) / 2);
_firstTimeOpened = false;
}
else
@@ -233,9 +241,10 @@ 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));
LayoutContainer.SetPosition(this, (0, (Parent!.Size.Y - DesiredSize.Y) / 2));
_firstTimeOpened = false;
}
else

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);
@@ -135,13 +148,6 @@ namespace Robust.Client.UserInterface.CustomControls
SearchBar.GrabKeyboardFocus();
}
public override void Close()
{
base.Close();
Dispose();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
@@ -188,6 +194,7 @@ namespace Robust.Client.UserInterface.CustomControls
private void OnEraseButtonToggled(BaseButton.ButtonToggledEventArgs args)
{
placementManager.ToggleEraser();
OverrideMenu.Disabled = args.Pressed;
}
private void BuildEntityList(string? searchStr = null)
@@ -225,8 +232,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 +401,7 @@ namespace Robust.Client.UserInterface.CustomControls
set
{
_totalItemCount = value;
MinimumSizeChanged();
InvalidateMeasure();
}
}
@@ -404,13 +411,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 +426,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 +468,6 @@ namespace Robust.Client.UserInterface.CustomControls
{
AddChild(ActualButton = new Button
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.FillExpand,
ToggleMode = true,
});
@@ -469,16 +477,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
})
@@ -496,6 +504,15 @@ namespace Robust.Client.UserInterface.CustomControls
}
EraseButton.Pressed = false;
OverrideMenu.Disabled = 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,17 +1,13 @@
<SS14Window xmlns="https://spacestation14.io">
<SS14Window xmlns="https://spacestation14.io" MinWidth="100" MinHeight="50">
<PanelContainer StyleClasses="windowPanel" />
<VBoxContainer SeparationOverride="0">
<PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<HBoxContainer>
<MarginContainer MarginLeftOverride="5" SizeFlagsHorizontal="FillExpand">
<Label Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
Text="{Loc Exemplary Window Title Here}" VAlign="Center" StyleClasses="windowTitle" />
</MarginContainer>
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" SizeFlagsVertical="ShrinkCenter" />
<Label Margin="5 0 0 0" HorizontalExpand="true" Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
Text="{Loc 'ss14window-placeholder-title'}" VAlign="Center" StyleClasses="windowTitle" />
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" VerticalAlignment="Center" />
</HBoxContainer>
</PanelContainer>
<MarginContainer Name="ContentsContainer" MarginBottomOverride="10" MarginLeftOverride="10"
MarginRightOverride="10" MarginTopOverride="10" RectClipContent="True"
SizeFlagsVertical="FillExpand" />
<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;

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