2020-10-08 17:41:23 +02:00
#nullable enable
using System ;
2018-08-02 08:29:55 +02:00
using System.Collections.Generic ;
using System.Linq ;
2020-06-15 12:30:11 -07:00
using Content.Client.GameObjects.EntitySystems ;
2020-10-08 17:41:23 +02:00
using Content.Client.Utility ;
2018-08-02 08:29:55 +02:00
using Content.Shared.Construction ;
2020-10-08 17:41:23 +02:00
using Content.Shared.GameObjects.Components ;
2020-04-29 13:43:07 +02:00
using Content.Shared.GameObjects.Components.Interactable ;
2020-10-08 17:41:23 +02:00
using Content.Shared.Materials ;
2019-04-15 21:11:38 -06:00
using Robust.Client.Graphics ;
using Robust.Client.Interfaces.Placement ;
using Robust.Client.Interfaces.ResourceManagement ;
using Robust.Client.Placement ;
using Robust.Client.ResourceManagement ;
2020-10-08 17:41:23 +02:00
using Robust.Client.UserInterface ;
2019-04-15 21:11:38 -06:00
using Robust.Client.UserInterface.Controls ;
using Robust.Client.UserInterface.CustomControls ;
using Robust.Client.Utility ;
using Robust.Shared.Enums ;
2020-10-08 17:41:23 +02:00
using Robust.Shared.GameObjects.Systems ;
2020-06-15 12:30:11 -07:00
using Robust.Shared.Interfaces.GameObjects ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.IoC ;
2020-10-08 17:41:23 +02:00
using Robust.Shared.Localization ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.Maths ;
using Robust.Shared.Prototypes ;
2018-08-02 08:29:55 +02:00
namespace Content.Client.Construction
{
public class ConstructionMenu : SS14Window
{
2020-08-24 14:10:28 +02:00
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
[Dependency] private readonly IResourceCache _resourceCache = default ! ;
[Dependency] private readonly IEntitySystemManager _systemManager = default ! ;
2020-10-08 17:41:23 +02:00
[Dependency] private readonly IPlacementManager _placementManager = default ! ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
protected override Vector2 ? CustomSize = > ( 720 , 320 ) ;
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
private ConstructionPrototype ? _selected ;
private string [ ] _categories = Array . Empty < string > ( ) ;
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
private readonly ItemList _recipes ;
private readonly ItemList _stepList ;
private readonly Button _buildButton ;
private readonly Button _eraseButton ;
private readonly LineEdit _searchBar ;
private readonly OptionButton _category ;
private readonly TextureRect _targetTexture ;
private readonly RichTextLabel _targetName ;
private readonly RichTextLabel _targetDescription ;
2019-12-05 16:00:03 +01:00
2019-05-28 00:30:34 +02:00
public ConstructionMenu ( )
2019-05-24 16:24:32 -04:00
{
2018-08-02 08:29:55 +02:00
IoCManager . InjectDependencies ( this ) ;
2020-10-08 17:41:23 +02:00
_placementManager . PlacementChanged + = PlacementChanged ;
2018-08-02 08:29:55 +02:00
Title = "Construction" ;
2019-05-15 17:37:44 -04:00
2020-10-08 17:41:23 +02:00
var hbox = new HBoxContainer ( ) { SizeFlagsHorizontal = SizeFlags . FillExpand } ;
2019-05-15 17:37:44 -04:00
2020-10-08 17:41:23 +02:00
var recipeContainer = new VBoxContainer ( ) { SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.45f } ;
2019-05-15 17:37:44 -04:00
2020-10-08 17:41:23 +02:00
var searchContainer = new HBoxContainer ( ) { SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.1f } ;
_searchBar = new LineEdit ( ) { PlaceHolder = "Search" , SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.6f } ;
_category = new OptionButton ( ) { SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.4f } ;
2019-05-15 17:37:44 -04:00
2020-10-08 17:41:23 +02:00
_recipes = new ItemList ( ) { SelectMode = ItemList . ItemListSelectMode . Single , SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.9f } ;
2019-05-15 17:37:44 -04:00
2020-10-08 17:41:23 +02:00
var spacer = new Control ( ) { SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.05f } ;
2019-05-15 17:37:44 -04:00
2020-10-08 17:41:23 +02:00
var stepsContainer = new VBoxContainer ( ) { SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.45f } ;
var targetContainer = new HBoxContainer ( ) { Align = BoxContainer . AlignMode . Center , SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.25f } ;
_targetTexture = new TextureRect ( ) { SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.15f , Stretch = TextureRect . StretchMode . KeepCentered } ;
var targetInfoContainer = new VBoxContainer ( ) { SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.85f } ;
_targetName = new RichTextLabel ( ) { SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.1f } ;
_targetDescription = new RichTextLabel ( ) { SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.9f } ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
_stepList = new ItemList ( ) { SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.75f , SelectMode = ItemList . ItemListSelectMode . None } ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
var buttonContainer = new VBoxContainer ( ) { SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.1f } ;
_buildButton = new Button ( ) { Disabled = true , ToggleMode = true , Text = Loc . GetString ( "Place construction ghost" ) , SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.5f } ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
var eraseContainer = new HBoxContainer ( ) { SizeFlagsVertical = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.5f } ;
_eraseButton = new Button ( ) { Text = Loc . GetString ( "Eraser Mode" ) , ToggleMode = true , SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.7f } ;
var clearButton = new Button ( ) { Text = Loc . GetString ( "Clear All" ) , SizeFlagsHorizontal = SizeFlags . FillExpand , SizeFlagsStretchRatio = 0.3f } ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
recipeContainer . AddChild ( searchContainer ) ;
recipeContainer . AddChild ( _recipes ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
searchContainer . AddChild ( _searchBar ) ;
searchContainer . AddChild ( _category ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
targetInfoContainer . AddChild ( _targetName ) ;
targetInfoContainer . AddChild ( _targetDescription ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
targetContainer . AddChild ( _targetTexture ) ;
targetContainer . AddChild ( targetInfoContainer ) ;
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
stepsContainer . AddChild ( targetContainer ) ;
stepsContainer . AddChild ( _stepList ) ;
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
eraseContainer . AddChild ( _eraseButton ) ;
eraseContainer . AddChild ( clearButton ) ;
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
buttonContainer . AddChild ( _buildButton ) ;
buttonContainer . AddChild ( eraseContainer ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
stepsContainer . AddChild ( buttonContainer ) ;
hbox . AddChild ( recipeContainer ) ;
hbox . AddChild ( spacer ) ;
hbox . AddChild ( stepsContainer ) ;
Contents . AddChild ( hbox ) ;
_recipes . OnItemSelected + = RecipeSelected ;
_recipes . OnItemDeselected + = RecipeDeselected ;
_searchBar . OnTextChanged + = SearchTextChanged ;
_category . OnItemSelected + = CategorySelected ;
_buildButton . OnToggled + = BuildButtonToggled ;
clearButton . OnPressed + = ClearAllButtonPressed ;
_eraseButton . OnToggled + = EraseButtonToggled ;
PopulateCategories ( ) ;
PopulateAll ( ) ;
}
private void PlacementChanged ( object? sender , EventArgs e )
{
_buildButton . Pressed = false ;
_eraseButton . Pressed = false ;
}
private void PopulateAll ( )
{
foreach ( var recipe in _prototypeManager . EnumeratePrototypes < ConstructionPrototype > ( ) )
{
_recipes . Add ( GetItem ( recipe , _recipes ) ) ;
2018-08-02 08:29:55 +02:00
}
}
2020-10-08 17:41:23 +02:00
private static ItemList . Item GetItem ( ConstructionPrototype recipe , ItemList itemList )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
return new ItemList . Item ( itemList )
{
Metadata = recipe ,
Text = recipe . Name ,
Icon = recipe . Icon . Frame0 ( ) ,
TooltipEnabled = true ,
TooltipText = recipe . Description ,
} ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
private void PopulateBy ( string search , string category )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
_recipes . Clear ( ) ;
foreach ( var recipe in _prototypeManager . EnumeratePrototypes < ConstructionPrototype > ( ) )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
if ( ! string . IsNullOrEmpty ( search ) )
2020-06-05 22:40:27 +02:00
{
2020-10-08 17:41:23 +02:00
if ( ! recipe . Name . ToLowerInvariant ( ) . Contains ( search . Trim ( ) . ToLowerInvariant ( ) ) )
continue ;
2020-06-05 22:40:27 +02:00
}
2020-10-08 17:41:23 +02:00
if ( ! string . IsNullOrEmpty ( category ) & & category ! = Loc . GetString ( "All" ) )
2020-06-05 22:40:27 +02:00
{
2020-10-08 17:41:23 +02:00
if ( recipe . Category ! = category )
continue ;
2020-06-05 22:40:27 +02:00
}
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
_recipes . Add ( GetItem ( recipe , _recipes ) ) ;
2020-06-05 22:40:27 +02:00
}
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
private void PopulateCategories ( )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
var uniqueCategories = new HashSet < string > ( ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
// hard-coded to show all recipes
uniqueCategories . Add ( Loc . GetString ( "All" ) ) ;
2018-08-02 08:29:55 +02:00
2020-06-15 12:30:11 -07:00
foreach ( var prototype in _prototypeManager . EnumeratePrototypes < ConstructionPrototype > ( ) )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
var category = Loc . GetString ( prototype . Category ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
if ( ! string . IsNullOrEmpty ( category ) )
uniqueCategories . Add ( category ) ;
}
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
_category . Clear ( ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
var array = uniqueCategories . ToArray ( ) ;
Array . Sort ( array ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
for ( var i = 0 ; i < array . Length ; i + + )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
var category = array [ i ] ;
_category . AddItem ( category , i ) ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
_categories = array ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
private void PopulateInfo ( ConstructionPrototype prototype )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
ClearInfo ( ) ;
var isItem = prototype . Type = = ConstructionType . Item ;
_buildButton . Disabled = false ;
_buildButton . Text = Loc . GetString ( ! isItem ? "Place construction ghost" : "Craft" ) ;
_targetName . SetMessage ( prototype . Name ) ;
_targetDescription . SetMessage ( prototype . Description ) ;
_targetTexture . Texture = prototype . Icon . Frame0 ( ) ;
if ( ! _prototypeManager . TryIndex ( prototype . Graph , out ConstructionGraphPrototype graph ) )
return ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
var startNode = graph . Nodes [ prototype . StartNode ] ;
var targetNode = graph . Nodes [ prototype . TargetNode ] ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
var path = graph . Path ( startNode . Name , targetNode . Name ) ;
var current = startNode ;
var stepNumber = 1 ;
Texture ? GetTextureForStep ( ConstructionGraphStep step )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
switch ( step )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
case MaterialConstructionGraphStep materialStep :
switch ( materialStep . Material )
{
case StackType . Metal :
return _resourceCache . GetTexture ( "/Textures/Objects/Materials/sheets.rsi/metal.png" ) ;
case StackType . Glass :
return _resourceCache . GetTexture ( "/Textures/Objects/Materials/sheets.rsi/glass.png" ) ;
case StackType . Plasteel :
return _resourceCache . GetTexture ( "/Textures/Objects/Materials/sheets.rsi/plasteel.png" ) ;
case StackType . Phoron :
return _resourceCache . GetTexture ( "/Textures/Objects/Materials/sheets.rsi/phoron.png" ) ;
case StackType . Cable :
return _resourceCache . GetTexture ( "/Textures/Objects/Tools/cables.rsi/coil-30.png" ) ;
}
break ;
case ToolConstructionGraphStep toolStep :
switch ( toolStep . Tool )
{
case ToolQuality . Anchoring :
return _resourceCache . GetTexture ( "/Textures/Objects/Tools/wrench.rsi/icon.png" ) ;
case ToolQuality . Prying :
return _resourceCache . GetTexture ( "/Textures/Objects/Tools/crowbar.rsi/icon.png" ) ;
case ToolQuality . Screwing :
return _resourceCache . GetTexture ( "/Textures/Objects/Tools/screwdriver.rsi/screwdriver-map.png" ) ;
case ToolQuality . Cutting :
return _resourceCache . GetTexture ( "/Textures/Objects/Tools/wirecutters.rsi/cutters-map.png" ) ;
case ToolQuality . Welding :
return _resourceCache . GetTexture ( "/Textures/Objects/Tools/welder.rsi/welder.png" ) ;
case ToolQuality . Multitool :
return _resourceCache . GetTexture ( "/Textures/Objects/Tools/multitool.rsi/multitool.png" ) ;
}
break ;
case ComponentConstructionGraphStep componentStep :
return componentStep . Icon ? . Frame0 ( ) ;
case PrototypeConstructionGraphStep prototypeStep :
return prototypeStep . Icon ? . Frame0 ( ) ;
case NestedConstructionGraphStep _ :
return null ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
return null ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
foreach ( var node in path )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
var edge = current . GetEdge ( node . Name ) ;
var firstNode = current = = startNode ;
if ( firstNode )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
_stepList . AddItem ( isItem
? Loc . GetString ( $"{stepNumber++}. To craft this item, you need:" )
: Loc . GetString ( $"{stepNumber++}. To build this, first you need:" ) ) ;
}
foreach ( var step in edge . Steps )
{
var icon = GetTextureForStep ( step ) ;
switch ( step )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
case MaterialConstructionGraphStep materialStep :
_stepList . AddItem (
! firstNode
? Loc . GetString (
"{0}. Add {1}x {2}." , stepNumber + + , materialStep . Amount , materialStep . Material )
: Loc . GetString ( " {0}x {1}" , materialStep . Amount , materialStep . Material ) , icon ) ;
break ;
case ToolConstructionGraphStep toolStep :
_stepList . AddItem ( Loc . GetString ( "{0}. Use a {1}." , stepNumber + + , toolStep . Tool . GetToolName ( ) ) , icon ) ;
break ;
case PrototypeConstructionGraphStep prototypeStep :
_stepList . AddItem ( Loc . GetString ( "{0}. Add {1}." , stepNumber + + , prototypeStep . Name ) , icon ) ;
break ;
case ComponentConstructionGraphStep componentStep :
_stepList . AddItem ( Loc . GetString ( "{0}. Add {1}." , stepNumber + + , componentStep . Name ) , icon ) ;
break ;
case NestedConstructionGraphStep nestedStep :
var parallelNumber = 1 ;
_stepList . AddItem ( Loc . GetString ( "{0}. In parallel..." , stepNumber + + ) ) ;
foreach ( var steps in nestedStep . Steps )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
var subStepNumber = 1 ;
foreach ( var subStep in steps )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
icon = GetTextureForStep ( subStep ) ;
switch ( subStep )
{
case MaterialConstructionGraphStep materialStep :
if ( ! isItem )
_stepList . AddItem ( Loc . GetString ( " {0}.{1}.{2}. Add {3}x {4}." , stepNumber , parallelNumber , subStepNumber + + , materialStep . Amount , materialStep . Material ) , icon ) ;
break ;
case ToolConstructionGraphStep toolStep :
_stepList . AddItem ( Loc . GetString ( " {0}.{1}.{2}. Use a {3}." , stepNumber , parallelNumber , subStepNumber + + , toolStep . Tool . GetToolName ( ) ) , icon ) ;
break ;
case PrototypeConstructionGraphStep prototypeStep :
_stepList . AddItem ( Loc . GetString ( " {0}.{1}.{2}. Add {3}." , stepNumber , parallelNumber , subStepNumber + + , prototypeStep . Name ) , icon ) ;
break ;
case ComponentConstructionGraphStep componentStep :
_stepList . AddItem ( Loc . GetString ( " {0}.{1}.{2}. Add {3}." , stepNumber , parallelNumber , subStepNumber + + , componentStep . Name ) , icon ) ;
break ;
}
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
parallelNumber + + ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
break ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
}
current = node ;
}
}
private void ClearInfo ( )
{
_buildButton . Disabled = true ;
_targetName . SetMessage ( string . Empty ) ;
_targetDescription . SetMessage ( string . Empty ) ;
_targetTexture . Texture = null ;
_stepList . Clear ( ) ;
}
private void RecipeSelected ( ItemList . ItemListSelectedEventArgs obj )
{
_selected = ( ConstructionPrototype ) obj . ItemList [ obj . ItemIndex ] . Metadata ! ;
PopulateInfo ( _selected ) ;
}
private void RecipeDeselected ( ItemList . ItemListDeselectedEventArgs obj )
{
_selected = null ;
ClearInfo ( ) ;
}
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
private void CategorySelected ( OptionButton . ItemSelectedEventArgs obj )
{
_category . SelectId ( obj . Id ) ;
PopulateBy ( _searchBar . Text , _categories [ obj . Id ] ) ;
}
private void SearchTextChanged ( LineEdit . LineEditEventArgs obj )
{
PopulateBy ( _searchBar . Text , _categories [ _category . SelectedId ] ) ;
}
private void BuildButtonToggled ( BaseButton . ButtonToggledEventArgs args )
{
if ( args . Pressed )
{
if ( _selected = = null ) return ;
var constructSystem = EntitySystem . Get < ConstructionSystem > ( ) ;
if ( _selected . Type = = ConstructionType . Item )
{
constructSystem . TryStartItemConstruction ( _selected . ID ) ;
_buildButton . Pressed = false ;
return ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
_placementManager . BeginPlacing ( new PlacementInformation ( )
{
IsTile = false ,
PlacementOption = _selected . PlacementMode ,
} , new ConstructionPlacementHijack ( constructSystem , _selected ) ) ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
else
{
_placementManager . Clear ( ) ;
}
_buildButton . Pressed = args . Pressed ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
private void EraseButtonToggled ( BaseButton . ButtonToggledEventArgs args )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
if ( args . Pressed ) _placementManager . Clear ( ) ;
_placementManager . ToggleEraserHijacked ( new ConstructionPlacementHijack ( _systemManager . GetEntitySystem < ConstructionSystem > ( ) , null ) ) ;
_eraseButton . Pressed = args . Pressed ;
2018-08-02 08:29:55 +02:00
}
2020-10-08 17:41:23 +02:00
private void ClearAllButtonPressed ( BaseButton . ButtonEventArgs obj )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
var constructionSystem = EntitySystem . Get < ConstructionSystem > ( ) ;
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
constructionSystem . ClearAllGhosts ( ) ;
}
2019-08-14 22:04:35 +02:00
2020-10-08 17:41:23 +02:00
protected override void Dispose ( bool disposing )
{
base . Dispose ( disposing ) ;
2018-08-02 08:29:55 +02:00
2020-10-08 17:41:23 +02:00
if ( disposing )
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
_placementManager . PlacementChanged - = PlacementChanged ;
2018-08-02 08:29:55 +02:00
}
}
}
}