Skip to content

Commit

Permalink
✨ Remove Entity Component from Entity (#286)
Browse files Browse the repository at this point in the history
* Initial

* Added unit tests and fixed up issues.

* Added documentation
  • Loading branch information
softwareantics authored Dec 2, 2023
1 parent c350565 commit 72c2dfa
Show file tree
Hide file tree
Showing 23 changed files with 513 additions and 123 deletions.
1 change: 1 addition & 0 deletions FinalEngine.Editor.Desktop/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<ResourceDictionary Source="Styles/Controls/ToggleButtonStyle.xaml" />
<ResourceDictionary Source="Styles/Controls/TransparentListBoxStyle.xaml" />
<ResourceDictionary Source="Styles/Controls/TransparentListBoxItemStyle.xaml" />
<ResourceDictionary Source="Styles/Controls/IconButtonStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Expand Down
7 changes: 7 additions & 0 deletions FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Button x:Class="FinalEngine.Editor.Desktop.Controls.Common.IconButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Image x:Name="image" Width="16" Height="16" />
</Grid>
</Button>
34 changes: 34 additions & 0 deletions FinalEngine.Editor.Desktop/Controls/Common/IconButton.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// <copyright file="IconButton.xaml.cs" company="Software Antics">
// Copyright (c) Software Antics. All rights reserved.
// </copyright>

namespace FinalEngine.Editor.Desktop.Controls.Common;

using System.Windows.Controls;
using System.Windows.Media;

/// <summary>
/// Interaction logic for IconButton.xaml.
/// </summary>
public partial class IconButton : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="IconButton"/> class.
/// </summary>
public IconButton()
{
this.InitializeComponent();
}

/// <summary>
/// Gets or sets the URI source for the icon.
/// </summary>
/// <value>
/// The URI source for the icon.
/// </value>
public ImageSource UriSource
{
get { return this.image.Source; }
set { this.image.Source = value; }
}
}
7 changes: 7 additions & 0 deletions FinalEngine.Editor.Desktop/FinalEngine.Editor.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</PropertyGroup>

<ItemGroup>
<None Remove="Resources\Images\Icons\Settings.png" />
<None Remove="Resources\Images\Splashes\splash.png" />
</ItemGroup>

Expand Down Expand Up @@ -48,6 +49,12 @@
<ProjectReference Include="..\FinalEngine.Rendering.OpenGL\FinalEngine.Rendering.OpenGL.csproj" />
</ItemGroup>

<ItemGroup>
<Resource Include="Resources\Images\Icons\Settings.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
</ItemGroup>

<ItemGroup>
<SplashScreen Include="Resources\Images\Splashes\splash.png" />
</ItemGroup>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions FinalEngine.Editor.Desktop/Styles/Controls/IconButtonStyle.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="clr-namespace:FinalEngine.Editor.Desktop.Controls.Common"
mc:Ignorable="d">
<Style TargetType="{x:Type c:IconButton}" BasedOn="{StaticResource MahApps.Styles.Button.VisualStudio}">
<Style.Setters>
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="MinWidth" Value="24" />
<Setter Property="MinHeight" Value="24" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource MahApps.Brushes.Button.Square.Background.MouseOver}" />
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Background" Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,26 @@
xmlns:dtv="clr-namespace:FinalEngine.Editor.Desktop.Views.Editing.DataTypes"
xmlns:dt="clr-namespace:FinalEngine.Editor.ViewModels.Editing.DataTypes;assembly=FinalEngine.Editor.ViewModels"
xmlns:vm="clr-namespace:FinalEngine.Editor.ViewModels.Inspectors;assembly=FinalEngine.Editor.ViewModels"
xmlns:c="clr-namespace:FinalEngine.Editor.Desktop.Controls.Common"
d:DataContext="{d:DesignInstance Type=vm:EntityComponentViewModel}"
mc:Ignorable="d">

<UserControl.Resources>
<BitmapImage x:Key="Icon_Settings" UriSource="/Resources/Images/Icons/Settings.png" />
</UserControl.Resources>

<Grid Margin="5">
<StackPanel>
<ToggleButton Content="{Binding Name}" Command="{Binding ToggleCommand}" />
<DockPanel>
<ToggleButton DockPanel.Dock="Left" HorizontalAlignment="Left" Content="{Binding Name}" Command="{Binding ToggleCommand}" />
<c:IconButton DockPanel.Dock="Right" HorizontalAlignment="Right" UriSource="{StaticResource Icon_Settings}">
<c:IconButton.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove Component" Command="{Binding RemoveCommand}" />
</ContextMenu>
</c:IconButton.ContextMenu>
</c:IconButton>
</DockPanel>

<ListBox ItemsSource="{Binding PropertyViewModels, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding IsVisible, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToVisibilityConverter}}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Platforms>x64</Platforms>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// <copyright file="EntityComponentViewModel.cs" company="Software Antics">
// Copyright (c) Software Antics. All rights reserved.
// Copyright (c) Software Antics. All rights reserved.
// </copyright>

namespace FinalEngine.Editor.ViewModels.Inspectors;
Expand All @@ -13,47 +13,75 @@ namespace FinalEngine.Editor.ViewModels.Inspectors;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using FinalEngine.ECS;
using FinalEngine.ECS.Components.Core;
using FinalEngine.Editor.ViewModels.Editing.DataTypes;
using FinalEngine.Editor.ViewModels.Exceptions.Inspectors;
using FinalEngine.Editor.ViewModels.Messages.Entities;

/// <summary>
/// Provides a standard implementation of an <see cref="IEntityComponentViewModel"/>.
/// Provides a standard implementation of an <see cref="IEntityComponentViewModel"/>.
/// </summary>
/// <seealso cref="ObservableObject" />
/// <seealso cref="IEntityComponentViewModel" />
/// <seealso cref="ObservableObject"/>
/// <seealso cref="IEntityComponentViewModel"/>
public sealed class EntityComponentViewModel : ObservableObject, IEntityComponentViewModel
{
/// <summary>
/// The property view models associated with this component model.
/// The component to model.
/// </summary>
private readonly IEntityComponent component;

/// <summary>
/// The entity that contains the component to be modeled.
/// </summary>
private readonly Entity entity;

/// <summary>
/// The messenger, used to notify other views when the entity has been modified.
/// </summary>
private readonly IMessenger messenger;

/// <summary>
/// The property view models associated with this component model.
/// </summary>
private readonly ObservableCollection<ObservableObject> propertyViewModels;

/// <summary>
/// Indicates whether the components properties are visible.
/// Indicates whether the components properties are visible.
/// </summary>
private bool isVisible;

/// <summary>
/// The toggle command.
/// The remove command, used to remove the component from the entity.
/// </summary>
private ICommand? removeCommand;

/// <summary>
/// The toggle command.
/// </summary>
private ICommand? toggleCommand;

/// <summary>
/// Initializes a new instance of the <see cref="EntityComponentViewModel"/> class.
/// Initializes a new instance of the <see cref="EntityComponentViewModel"/> class.
/// </summary>
/// <param name="messenger">
/// The messenger.
/// </param>
/// <param name="component">
/// The component to be modelled.
/// The component to be modeled.
/// </param>
/// <param name="entity">
/// The entity that contains the specified <paramref name="component"/>.
/// </param>
/// <exception cref="ArgumentNullException">
/// The specified <paramref name="component"/> parameter cannot be null.
/// The specified <paramref name="messenger"/>, <paramref name="entity"/> or <paramref name="component"/> parameter cannot be null.
/// </exception>
public EntityComponentViewModel(IEntityComponent component)
public EntityComponentViewModel(IMessenger messenger, Entity entity, IEntityComponent component)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
this.messenger = messenger ?? throw new ArgumentNullException(nameof(messenger));
this.entity = entity ?? throw new ArgumentNullException(nameof(entity));
this.component = component ?? throw new ArgumentNullException(nameof(component));

this.propertyViewModels = new ObservableCollection<ObservableObject>();

Expand Down Expand Up @@ -138,14 +166,48 @@ public ICollection<ObservableObject> PropertyViewModels
get { return this.propertyViewModels; }
}

/// <inheritdoc/>
public ICommand RemoveCommand
{
get { return this.removeCommand ??= new RelayCommand(this.Remove, this.CanRemove); }
}

/// <inheritdoc/>
public ICommand ToggleCommand
{
get { return this.toggleCommand ??= new RelayCommand(this.Toggle); }
}

/// <summary>
/// Toggles the visibility of the components properties.
/// Determines whether the component can be removed from the entity.
/// </summary>
/// <returns>
/// <c>true</c> if the component can be removed from the entity; otherwise, <c>false</c>.
/// </returns>
private bool CanRemove()
{
return this.component.GetType() != typeof(TagComponent);
}

/// <summary>
/// Removes the component from the entity and notifies other views that the entity has been modified.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="Entity"/> provided to this instance does not contain an <see cref="IEntityComponent"/> of the required type.
/// </exception>
private void Remove()
{
if (!this.entity.ContainsComponent(this.component))
{
throw new InvalidOperationException($"The {nameof(Entity)} provided to this instance does not contain an {nameof(IEntityComponent)} of type: '{this.component.GetType()}'");
}

this.entity.RemoveComponent(this.component);
this.messenger.Send(new EntityModifiedMessage(this.entity));
}

/// <summary>
/// Toggles the visibility of the components properties.
/// </summary>
private void Toggle()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// <copyright file="EntityInspectorViewModel.cs" company="Software Antics">
// Copyright (c) Software Antics. All rights reserved.
// Copyright (c) Software Antics. All rights reserved.
// </copyright>

namespace FinalEngine.Editor.ViewModels.Inspectors;
Expand All @@ -8,48 +8,90 @@ namespace FinalEngine.Editor.ViewModels.Inspectors;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using FinalEngine.ECS;
using FinalEngine.Editor.ViewModels.Messages.Entities;

/// <summary>
/// Provides a standard implementation of an <see cref="IEntityInspectorViewModel"/>.
/// Provides a standard implementation of an <see cref="IEntityInspectorViewModel"/>.
/// </summary>
/// <seealso cref="ObservableObject" />
/// <seealso cref="IEntityInspectorViewModel" />
/// <seealso cref="ObservableObject"/>
/// <seealso cref="IEntityInspectorViewModel"/>
public sealed class EntityInspectorViewModel : ObservableObject, IEntityInspectorViewModel
{
/// <summary>
/// The component view models.
/// The component view models.
/// </summary>
private readonly ObservableCollection<IEntityComponentViewModel> componentViewModels;

/// <summary>
/// The entity being inspected.
/// The entity being inspected.
/// </summary>
private readonly Entity entity;

/// <summary>
/// Initializes a new instance of the <see cref="EntityInspectorViewModel"/> class.
/// The messenger.
/// </summary>
private readonly IMessenger messenger;

/// <summary>
/// Initializes a new instance of the <see cref="EntityInspectorViewModel"/> class.
/// </summary>
/// <param name="messenger">
/// The messenger.
/// </param>
/// <param name="entity">
/// The entity to be inspected.
/// The entity to be inspected.
/// </param>
/// <exception cref="ArgumentNullException">
/// The specified <paramref name="entity"/> parameter cannot be null.
/// The specified <paramref name="entity"/> parameter cannot be null.
/// </exception>
public EntityInspectorViewModel(Entity entity)
public EntityInspectorViewModel(IMessenger messenger, Entity entity)
{
this.messenger = messenger ?? throw new ArgumentNullException(nameof(messenger));
this.entity = entity ?? throw new ArgumentNullException(nameof(entity));
this.componentViewModels = new ObservableCollection<IEntityComponentViewModel>();

foreach (var component in this.entity.Components)
{
this.componentViewModels.Add(new EntityComponentViewModel(component));
}
this.messenger.Register<EntityModifiedMessage>(this, this.HandleEntityModified);

this.InitializeEntityComponents();
}

/// <inheritdoc/>
public ICollection<IEntityComponentViewModel> ComponentViewModels
{
get { return this.componentViewModels; }
}

/// <summary>
/// Handles the <see cref="EntityModifiedMessage"/> and initializes the entity component view models.
/// </summary>
/// <param name="recipient">
/// The recipient.
/// </param>
/// <param name="message">
/// The message.
/// </param>
private void HandleEntityModified(object recipient, EntityModifiedMessage message)
{
if (!ReferenceEquals(this.entity, message.Entity))
{
return;
}

this.InitializeEntityComponents();
}

/// <summary>
/// Initializes the entity component view models for the <see cref="Entity"/>.
/// </summary>
private void InitializeEntityComponents()
{
this.componentViewModels.Clear();

foreach (var component in this.entity.Components)
{
this.componentViewModels.Add(new EntityComponentViewModel(this.messenger, this.entity, component));
}
}
}
Loading

0 comments on commit 72c2dfa

Please sign in to comment.