Changeset - 6a6817b17a06
[Not reviewed]
default
0 2 0
Jason Maltzen - 11 months ago 2021-09-10 00:05:03
jason@hiddenachievement.com
Simulator view updates: new warning when the recipe is below minimum concentration. Add the missing reactions to the warning about missing reactions. Show the current saved recipe for a color, and allow replacing/saving the current simulated recipe as the recipe for that color.
2 files changed with 165 insertions and 10 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 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;
 

	
 
            RecipeItems.RemoveAt(pos);
 
            RecipeItems.Insert(pos - 1, item);
 

	
 
            Refresh();
 
        }
 

	
 
        public void MoveItemDown(RecipeItem item)
 
        {
 
            int pos = RecipeItems.IndexOf(item);
 
            if ((pos < 0) || (pos >= RecipeItems.Count - 1)) return;
 

	
 
            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);
 
                    ExistingRecipe = null;
 
                    ExistingRecipe = existingRecipe;
 
                }
 
                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/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">
 

	
 
  <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 DATA" Message="You are missing reaction data necessary for simulating this recipe." IsVisible="{Binding HasMissingReactions}" />
 
        
 
        <views:PaintSwatchView DockPanel.Dock="Bottom" ShowName="True" Color="{Binding PaintColor}"  IsVisible="{Binding !HasMissingReactions}"/>
 

	
 
      <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}"/>
 
      </Grid>
 
    </Border>
 

	
 
        <Grid ColumnDefinitions="200,15,*" RowDefinitions="20,*" VerticalAlignment="Stretch" Margin="0 0 0 15">
 
    <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>
0 comments (0 inline, 0 general)