Changeset - 3737f942b229
[Not reviewed]
default
0 2 2
Jason Maltzen - 11 months ago 2023-12-03 06:01:47
jason@hiddenachievement.com
Simulator view now uses a new recipe view element instead of the old content element.
4 files changed with 152 insertions and 15 deletions:
0 comments (0 inline, 0 general)
ViewModels/SimulatorViewModel.cs
Show inline comments
 
using System;
 
using System.Collections.Generic;
 
using System.Collections.ObjectModel;
 
using System.Collections.Specialized;
 
using System.Reactive.Linq;
 
using Avalonia;
 
using Avalonia.Input.Platform;
 
using DesertPaintCodex.Models;
 
using DesertPaintCodex.Services;
 
using DynamicData;
 
using DynamicData.Binding;
 
using ReactiveUI;
 

	
 
namespace DesertPaintCodex.ViewModels
 
{
 
    public class SimulatorViewModel : ViewModelBase
 
    {
 
        private RecipeLibraryViewModel? _recipeLibraryVM;
 

	
 
        private ViewModelBase? _recipeLibraryActivity;
 
        public ViewModelBase? RecipeLibraryActivity { get => _recipeLibraryActivity; private set => this.RaiseAndSetIfChanged(ref _recipeLibraryActivity, value); }
 

	
 

	
 
        private PaintColor? _paintColor;
 
        public PaintColor? PaintColor
 
        {
 
            get => _paintColor;
 
            set => this.RaiseAndSetIfChanged(ref _paintColor, value);
 
        }
 

	
 
        // Stealing the recipe view from the  paint generator
 
        private GeneratorRecipe? _existingRecipe;
 
        public GeneratorRecipe? ExistingRecipe { get => _existingRecipe; private set => this.RaiseAndSetIfChanged(ref _existingRecipe, value); }
 
        private PaintRecipe? _existingRecipe;
 
        public PaintRecipe? ExistingRecipe { get => _existingRecipe; private set => this.RaiseAndSetIfChanged(ref _existingRecipe, value); }
 

	
 
        private bool _hasMissingReactions;
 
        public bool HasMissingReactions
 
        {
 
            get => _hasMissingReactions;
 
            set => this.RaiseAndSetIfChanged(ref _hasMissingReactions, value);
 
        }
 

	
 
        private bool _isGoodRecipe;
 

	
 
        public bool IsGoodRecipe
 
        {
 
            get => _isGoodRecipe;
 
            set => this.RaiseAndSetIfChanged(ref _isGoodRecipe, value);
 
        }
 

	
 
        private bool _isValidConcentration;
 
        public bool IsValidConcentration
 
        {
 
            get => _isValidConcentration;
 
            set => this.RaiseAndSetIfChanged(ref _isValidConcentration, value);
 
        }
 

	
 
        private string _missingReactionList = string.Empty;
 
        public string MissingReactionList
 
        {
 
            get => _missingReactionList;
 
            set => this.RaiseAndSetIfChanged(ref _missingReactionList, value);
 
        }
 

	
 
        public ObservableCollection<Reagent> Reagents { get; } = new();
 
        public ObservableCollection<Reagent> ActiveReagents { get; } = new();
 
        public ObservableCollection<RecipeItem> RecipeItems { get; } = new();
 

	
 
        
 
        private readonly PaintRecipe _currentRecipe = new();
 
        private PaintColor _tempColor = new PaintColor(0, 0,0);
 

	
 
        public SimulatorViewModel()
 
        {
 
            List<string> reagentNames = ReagentService.Names;
 
            for (int i = 0; i < reagentNames.Count; i++)
 
            {
 
                Reagents.Add(ReagentService.GetReagent(reagentNames[i]));
 
            }
 

	
 
            ActiveReagents.CollectionChanged += OnActiveReagentsChanged;
 

	
 
            RecipeItems
 
                .ToObservableChangeSet()
 
                .AutoRefresh(item => item.Quantity)
 
                .Subscribe(_ => Refresh());
 
        }
 

	
 
        public async void CopyToClipboard()
 
        {
 
            IClipboard clipboard = Application.Current.Clipboard;
 
            await clipboard.SetTextAsync(_currentRecipe.ToString());
 
        }
 

	
 
        public void MoveItemUp(RecipeItem item)
 
        {
 
            int pos = RecipeItems.IndexOf(item);
 
            if (pos <= 0) return;
...
 
@@ -110,141 +110,131 @@ namespace DesertPaintCodex.ViewModels
 
            RecipeItems.RemoveAt(pos);
 
            RecipeItems.Insert(pos + 1, item);
 

	
 
            Refresh();
 
        }
 

	
 
        private void ShowRecipeLibrary()
 
        {
 
            _recipeLibraryVM = new RecipeLibraryViewModel();
 
            Observable.Merge(_recipeLibraryVM.Ok, _recipeLibraryVM.Cancel).Subscribe(_ => LoadRecipe());
 

	
 
            RecipeLibraryActivity = _recipeLibraryVM;
 
        }
 

	
 

	
 
        // Show the list of recipes and allow the user to pick one to load
 
        public void LoadRecipe()
 
        {
 
            // TODO: show the list of recipes
 
        }
 

	
 
        public void SelectRecipeToLoad(PaintRecipe recipe)
 
        {
 
            // 1: Clear the current recipe
 
            _currentRecipe.Clear();
 
            RecipeItems.Clear();
 

	
 
            // 2: Set the recipe items from the loaded recipe
 
            foreach (PaintRecipe.ReagentQuantity reagentQuantity in recipe.Reagents)
 
            {
 
                Reagent reagent = ReagentService.GetReagent(reagentQuantity.Name);
 
                if (reagent != null)
 
                {
 
                    RecipeItems.Add(new RecipeItem(reagent, reagentQuantity.Quantity));
 
                }
 
            }
 
            UpdateRecipe();
 
        }
 

	
 
        // Replace the recipe for a color with the current recipe
 
        public void ReplaceRecipe()
 
        {
 
            if (IsGoodRecipe && IsValidConcentration)
 
            {
 
                if (ProfileManager.CurrentProfile == null) return;
 

	
 
                string colorName = PaletteService.FindNearest(_currentRecipe.ReactedColor);
 
                _tempColor.Set(colorName, _currentRecipe.ReactedColor);
 
                
 
                ProfileManager.CurrentProfile.Recipes[colorName].CopyFrom(_currentRecipe);
 

	
 
                UpdateExistingRecipe();
 
            }
 
        }
 

	
 
        private void UpdateExistingRecipe()
 
        {
 
            if (IsGoodRecipe)
 
            {
 
                string colorName = PaletteService.FindNearest(_currentRecipe.ReactedColor);
 
                if ((ProfileManager.CurrentProfile != null) && (ProfileManager.CurrentProfile.Recipes.TryGetValue(colorName, out PaintRecipe? paintRecipe)))
 
                {
 
                    System.Diagnostics.Debug.WriteLine($"Setting existing recipe for {colorName}.");
 

	
 
                    GeneratorRecipe? existingRecipe = ExistingRecipe;
 
                    if (existingRecipe == null)
 
                    {
 
                        _tempColor.Set(colorName, _currentRecipe.ReactedColor);
 
                        existingRecipe = new GeneratorRecipe(_tempColor);
 
                    }
 
                    else
 
                    {
 
                        existingRecipe.Color.Set(colorName, _currentRecipe.ReactedColor);
 
                    }
 
                    existingRecipe.DraftRecipe(paintRecipe);
 
                    PaintRecipe? existingRecipe = ExistingRecipe;
 
                    ExistingRecipe = null;
 
                    ExistingRecipe = existingRecipe;
 
                    ExistingRecipe = _currentRecipe;
 
                }
 
                else
 
                {
 
                    ExistingRecipe = null;
 
                }
 
            }
 
            else
 
            {
 
                ExistingRecipe = null;
 
            }
 
        }
 

	
 
        private void UpdateRecipe()
 
        {
 
            _currentRecipe.Clear();
 
            foreach (RecipeItem entry in RecipeItems)
 
            {
 
                if (!entry.Unused)
 
                {
 
                    _currentRecipe.AddReagent(entry.Reagent.Name, entry.Quantity);
 
                }
 
            }
 

	
 
            PaintColor = null; // TODO: Find a better way to kick the paint swatch when reassigning color from the same ref.
 
            PaintColor          = _currentRecipe.ReactedColor;
 
            HasMissingReactions = _currentRecipe.HasMissingReactions();
 
            if (HasMissingReactions)
 
            {
 
                List<(string, string)> missingReactions = _currentRecipe.MissingReactionPairs();
 
                List<string> missingReactionsStrings = new List<string>(missingReactions.Count);
 
                foreach ((string reagent1, string reagent2) in missingReactions)
 
                {
 
                    missingReactionsStrings.Add($"{reagent1}+{reagent2}");
 
                }
 
                MissingReactionList = string.Join(", ", missingReactionsStrings);
 
            }
 
            IsGoodRecipe        = !HasMissingReactions && _currentRecipe.IsValidForConcentration(10);
 
            IsValidConcentration = _currentRecipe.IsValidForConcentration(10);
 

	
 
            UpdateExistingRecipe();
 
        }
 

	
 
        private void OnActiveReagentsChanged(object? sender, NotifyCollectionChangedEventArgs e)
 
        {
 
            HashSet<Reagent> unmatchedReagents = new(ActiveReagents);
 
            
 
            for (int i = RecipeItems.Count - 1; i >= 0; i--)
 
            {
 
                bool found = false;
 
                foreach (Reagent reagent in ActiveReagents)
 
                {
 
                    if (reagent == RecipeItems[i].Reagent)
 
                    {
 
                        unmatchedReagents.Remove(reagent);
 
                        found = true;
 
                        break;
 
                    }
 
                }
 

	
 
                if (!found) RecipeItems.RemoveAt(i);
 
            }
 

	
 
            foreach (Reagent reagent in unmatchedReagents)
 
            {
Views/RecipeView.axaml
Show inline comments
 
new file 100644
 
<UserControl xmlns="https://github.com/avaloniaui"
 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 
             mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="60"
 
             xmlns:local="clr-namespace:DesertPaintCodex.Models"
 
             x:Class="DesertPaintCodex.Views.RecipeView">
 

	
 
  <UserControl.Styles>
 
    <Style Selector="TextBlock">
 
      <Setter Property="FontSize" Value="14"/>
 
      <Setter Property="VerticalAlignment" Value="Center"/>
 
    </Style>
 
  </UserControl.Styles>
 
  <Border Classes="ThinFrame">
 
    <DockPanel>
 
      <Grid ColumnDefinitions="45,5,130,5,*" RowDefinitions="Auto" Margin="5">
 
        <Border Grid.Row="0" Grid.Column="0" Classes="ReagentSwatch" Name="ColorSwatch"/>
 
        <StackPanel Grid.Column="2" VerticalAlignment="Center">
 
          <TextBlock TextWrapping="Wrap" Name="ColorName">Black</TextBlock>
 
          <TextBlock TextWrapping="Wrap" Name="Cost">Cost: 0</TextBlock>
 
        </StackPanel>
 
        <TextBlock Grid.Row="0" Grid.Column="4" VerticalAlignment="Center" TextWrapping="Wrap" Name="Ingredients">None</TextBlock>
 
      </Grid>
 
    </DockPanel>
 
  </Border>
 
</UserControl>
Views/RecipeView.axaml.cs
Show inline comments
 
new file 100644
 
using Avalonia;
 
using Avalonia.Controls;
 
using Avalonia.Markup.Xaml;
 
using Avalonia.Media;
 
using DesertPaintCodex.Models;
 
using DesertPaintCodex.Services;
 
using System.Text;
 

	
 
namespace DesertPaintCodex.Views
 
{
 
    public class RecipeView : UserControl
 
    {
 
        private static readonly SolidColorBrush NoColor = new();
 
        public static readonly DirectProperty<RecipeView, PaintColor?> ColorProperty = AvaloniaProperty.RegisterDirect<RecipeView, PaintColor?>(nameof(Color),
 
                o => o.Color,
 
                (o, v) => o.Color = v);
 

	
 
        private PaintColor? _color;
 
        public PaintColor? Color { get => _color; set => SetAndRaise(ColorProperty, ref _color, value); }
 

	
 
        public static readonly StyledProperty<string> IngredientsProperty =
 
            AvaloniaProperty.Register<RecipeView, string>(nameof(Ingredients));
 
        private string _ingredients = string.Empty;
 
        public string Ingredients { get => _ingredients; set => this.SetAndRaise(IngredientsProperty, ref _ingredients, value); }
 

	
 
        public static readonly DirectProperty<RecipeView, PaintRecipe?> RecipeProperty =
 
            AvaloniaProperty.RegisterDirect<RecipeView, PaintRecipe?>(nameof(Recipe),
 
                o => o.Recipe,
 
                (o,v) => o.Recipe = v);
 
        private PaintRecipe? _recipe;
 
        public PaintRecipe? Recipe
 
        {
 
            get => _recipe;
 
            set => this.SetAndRaise(RecipeProperty, ref _recipe, value);
 
        }
 

	
 
        private readonly Border _colorSwatch;
 
        private readonly TextBlock _colorName;
 
        private readonly TextBlock _ingredientList;
 
        private readonly TextBlock _cost;
 

	
 
        public RecipeView()
 
        {
 
            InitializeComponent();
 

	
 
            _colorSwatch = this.FindControl<Border>("ColorSwatch");
 
            _ingredientList = this.FindControl<TextBlock>("Ingredients");
 
            _colorName = this.FindControl<TextBlock>("ColorName");
 
            _cost = this.FindControl<TextBlock>("Cost");
 
            ColorProperty.Changed.AddClassHandler<RecipeView>((x, _) => x.UpdateColor());
 
            RecipeProperty.Changed.AddClassHandler<RecipeView>((x, _) => x.UpdateRecipe());
 
            UpdateRecipe();
 
        }
 

	
 
        private void InitializeComponent()
 
        {
 
            AvaloniaXamlLoader.Load(this);
 
        }
 

	
 
        private void UpdateColor()
 
        {
 
            UpdateColorSwatch();
 
            UpdateColorName();
 
        }
 

	
 
        private void UpdateColorSwatch()
 
        {
 
            _colorSwatch.Background = Color == null ? NoColor : new SolidColorBrush(new Color(0xFF, Color.Red, Color.Green, Color.Blue));
 
        }
 

	
 
        private void UpdateColorName()
 
        {
 
            _colorName.Text = Color == null ? "[Unknown]" : PaletteService.FindNearest(Color);
 
        }
 

	
 
        private void UpdateIngredients()
 
        {
 
            string ingredients = string.Empty;
 
            if (Recipe != null)
 
            {
 
                StringBuilder sb = new();
 
                for (int i = 0; i < Recipe.Reagents.Count; i++)
 
                {
 
                    sb.Append(Recipe.Reagents[i].Quantity);
 
                    sb.Append(' ');
 
                    sb.Append(Recipe.Reagents[i].Name);
 
                    if (i != Recipe.Reagents.Count - 1)
 
                    {
 
                        sb.Append(", ");
 
                    }
 
                }
 

	
 
                ingredients = sb.ToString();
 
            }
 

	
 
            _ingredientList.Text = ingredients;
 
        }
 

	
 
        private void UpdateCost()
 
        {
 
            _cost.Text = Recipe == null ? "Cost: 0" : $"Cost: {Recipe.Cost}";
 
        }
 

	
 
        private void UpdateRecipe()
 
        {
 
            if (Recipe != null)
 
            {
 
                // Update components from recipe
 
                Color = Recipe.ReactedColor;
 
            }
 
            else
 
            {
 
                Color = null;
 
            }
 
            UpdateIngredients();
 
            UpdateCost();
 
        }
 

	
 
    }
 
}
Views/SimulatorView.axaml
Show inline comments
 
<UserControl xmlns="https://github.com/avaloniaui"
 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 
             xmlns:vm="using:DesertPaintCodex.ViewModels"
 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 
             xmlns:views="clr-namespace:DesertPaintCodex.Views"
 
             xmlns:models="clr-namespace:DesertPaintCodex.Models"
 
             mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="475"
 
             x:Class="DesertPaintCodex.Views.SimulatorView">
 
    
 
    <Design.DataContext>
 
        <vm:SimulatorViewModel/>
 
    </Design.DataContext>
 
    
 
    <UserControl.DataContext>
 
        <vm:SimulatorViewModel />
 
    </UserControl.DataContext>
 

	
 
    <UserControl.DataTemplates>
 
      <DataTemplate DataType="{x:Type models:GeneratorRecipe}">
 
        <Grid ColumnDefinitions="45,5,130,5,*" RowDefinitions="Auto" Margin="5">
 
          <Border Grid.Column="0" Classes="ReagentSwatch" Background="{Binding Color, Converter={StaticResource paintToBrush}, FallbackValue=#00000000}" />
 
          <TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Color.Name}"/>
 
          <TextBlock Grid.Column="4" VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Recipe}"/>
 
        </Grid>
 
      </DataTemplate>
 
    </UserControl.DataTemplates>
 
  
 
    <UserControl.Styles>
 
        <Style Selector="Border.ReagentSwatch">
 
            <Setter Property="Width" Value="20"/>
 
            <Setter Property="Height" Value="20"/>
 
        </Style>
 
        <Style Selector="TextBlock.Unused">
 
            <Setter Property="Foreground" Value="#CA7091"/>
 
            <Setter Property="FontStyle" Value="Italic"/>
 
        </Style>
 
    </UserControl.Styles>
 

	
 
  <DockPanel Classes="Activity">
 
        <Button DockPanel.Dock="Bottom" VerticalAlignment="Center" Margin="0 10 0 0" IsEnabled="{Binding IsGoodRecipe}" Command="{Binding CopyToClipboard}">📋 Copy Recipe to Clipboard</Button>
 

	
 
      <views:EmbeddedWarningBox DockPanel.Dock="Bottom" Title="🛇 INSUFFICIENT CONCENTRATION" Message="The current recipe does not have sufficient concentration of reagents." IsVisible="{Binding !IsValidConcentration}" />
 
      <views:EmbeddedWarningBox DockPanel.Dock="Bottom" Title="🛇 INSUFFICIENT DATA" Message="You are missing reaction data necessary for simulating this recipe." Message2="{Binding MissingReactionList}" IsVisible="{Binding HasMissingReactions}" />
 

	
 
    <Border Classes="ThinFrame" DockPanel.Dock="Bottom" IsVisible="{Binding ExistingRecipe, Converter={x:Static ObjectConverters.IsNotNull}}" Margin="0,0,0,10">
 
      <Grid ColumnDefinitions="*,50" RowDefinitions="20,*" HorizontalAlignment="Stretch" DockPanel.Dock="Bottom" >
 
          <TextBlock Classes="BlockHeader" Grid.Row="0" Grid.Column="0" DockPanel.Dock="Top" Margin="0 0 0 5">CURRENT RECIPE</TextBlock>
 
          <ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding ExistingRecipe}"/>
 
          <views:RecipeView Grid.Row="1" Grid.Column="0" Recipe="{Binding ExistingRecipe}"/>
 
      </Grid>
 
    </Border>
 

	
 
    <Grid ColumnDefinitions="*,120" RowDefinitions="*" HorizontalAlignment="Stretch" DockPanel.Dock="Bottom" Margin="0,0,0,10">
 
      <views:PaintSwatchView Grid.Row="0" Grid.Column="0" ShowName="True" Color="{Binding PaintColor}"  IsVisible="true"/>
 
      <Button Grid.Row="0" Grid.Column="1" Margin="10 10 10 10" IsEnabled="{Binding IsGoodRecipe}"  Command="{Binding ReplaceRecipe}">
 
        <Panel>
 
        <TextBlock TextAlignment="Center" IsVisible="{Binding ExistingRecipe, Converter={x:Static ObjectConverters.IsNull}}">
 
Save
 
Recipe
 
        </TextBlock>
 
        <TextBlock TextAlignment="Center" IsVisible="{Binding ExistingRecipe, Converter={x:Static ObjectConverters.IsNotNull}}">
 
Replace
 
Recipe
 
        </TextBlock>
 
        </Panel>
 
      </Button>
 
    </Grid>
 

	
 
    <Grid ColumnDefinitions="200,15,*" RowDefinitions="20,*" VerticalAlignment="Stretch" Margin="0 0 0 15">
 
            <TextBlock Grid.Row="0" Grid.Column="0" DockPanel.Dock="Top" Classes="BlockHeader" Margin="0 0 0 5">REAGENTS</TextBlock>
 
            <ListBox Grid.Row="1" Grid.Column="0" Items="{Binding Reagents}" SelectedItems="{Binding ActiveReagents}" SelectionMode="Multiple,Toggle">
 
                <ListBox.ItemTemplate>
 
                    <DataTemplate>
 
                        <Border BorderBrush="{DynamicResource ThemeBorderLowBrush}" BorderThickness="1">
 
                            <CheckBox IsChecked="{Binding $parent[ListBoxItem].IsSelected}">
 
                                <StackPanel Orientation="Horizontal" Spacing="10">
 
                                    <Border Classes="ReagentSwatch" Background="{Binding Color, Converter={StaticResource paintToBrush}, FallbackValue=#00000000}" />
 
                                    <TextBlock Text="{Binding Name}"/>
 
                                </StackPanel>
 
                            </CheckBox>
 
                        </Border>
 
                    </DataTemplate>
 
                </ListBox.ItemTemplate>
 
            </ListBox>
 

	
 
            <TextBlock Grid.Row="0" Grid.Column="2" Classes="BlockHeader">RECIPE</TextBlock>
 
            <Border Grid.Row="1" Grid.Column="2"
 
                    BorderBrush="{DynamicResource ThemeBorderMidBrush}"
 
                    BorderThickness="{DynamicResource ThemeBorderThickness}">
 
                <ScrollViewer Background="{DynamicResource ThemeBackgroundBrush}">
 
                    <ItemsControl Items="{Binding RecipeItems}">
 
                        <ItemsControl.ItemTemplate>
 
                            <DataTemplate>
 
                                <Grid ColumnDefinitions="45,15,*,15,40,5,40" RowDefinitions="Auto" Margin="5">
 
                                    <NumericUpDown Grid.Column="0" Minimum="0" Maximum="{Binding MaxQty}" Value="{Binding Quantity}"/>
 
                                    <TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Reagent.Name}"
 
                                               FontSize="16" IsVisible="{Binding !Unused}"/>
 
                                    <TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Reagent.Name}" Classes="Unused"
 
                                               FontSize="16" IsVisible="{Binding Unused}"/>
 
                                    <Button Grid.Column="4" Command="{Binding $parent[views:SimulatorView].DataContext.MoveItemUp}"
 
                                            CommandParameter="{Binding}"
 
                                            IsEnabled="{Binding !First}">▲</Button>
 
                                    <Button Grid.Column="6" Command="{Binding $parent[views:SimulatorView].DataContext.MoveItemDown}"
 
                                            CommandParameter="{Binding}"
 
                                            IsEnabled="{Binding !Last}">▼</Button>
 
                                </Grid>
 
                            </DataTemplate>
 
                        </ItemsControl.ItemTemplate>
 
                    </ItemsControl>
 
                </ScrollViewer>
 
            </Border>
 
        </Grid>
 
    </DockPanel>
0 comments (0 inline, 0 general)