Changeset - f419334a476f
[Not reviewed]
Tess Snider (Malkyne) - 3 years ago 2021-07-19 15:16:24
this@malkyne.org
Improved up behavior for clipped reactions and 3-way reactions, and fixed
related bugs.
6 files changed with 74 insertions and 24 deletions:
0 comments (0 inline, 0 general)
Models/PlayerProfile.cs
Show inline comments
...
 
@@ -15,33 +15,33 @@ namespace DesertPaintCodex.Models
 
        
 
        private readonly string _reactFile;
 
        private readonly string _settingsFile;
 
        private readonly string _clipFile;
 

	
 
        private static readonly Regex _recipeHeaderRegex     = new(@"^--- Recipe: (?<colorname>(\w*\s)*\w+)\s*");
 
        private static readonly Regex _recipeIngredientRegex = new(@"(?<ingredient>(\w+\s)?\w+)\s*\|\s*(?<quantity>\d+)\s*");
 
        
 
        private Settings ProfileSettings { get; } = new();
 

	
 
        public string Directory { get; }
 

	
 
        public string Name { get; private set; }
 
        
 
        public ReactionSet Reactions { get; } = new();
 

	
 
        private Dictionary<string, Dictionary<string, ClipType>> Clippers { get; } = new();
 
        public Dictionary<string, Dictionary<string, ClipType>> Clippers { get; } = new();
 

	
 
        public string ReagentFile { get; }
 

	
 
        public Dictionary<string, PaintRecipe> Recipes { get; } = new();
 

	
 
        public Dictionary<string, PaintRecipe> RibbonRecipes { get; } = new();
 

	
 
        public int RecipeCount
 
        {
 
            get
 
            {
 
                int count = 0;
 
                foreach (PaintRecipe recipe in Recipes.Values)
 
                {
 
                    if (recipe.IsValidForConcentration(PaintRecipe.PaintRecipeMinConcentration))
 
                    {
...
 
@@ -296,33 +296,33 @@ namespace DesertPaintCodex.Models
 
                    if (tokens.Length == 5)
 
                    {
 
                        Reagent reagent1 = ReagentService.GetReagent(tokens[0].Trim());
 
                        Reagent reagent2 = ReagentService.GetReagent(tokens[1].Trim());
 
                        Reaction reaction = new(
 
                            int.Parse(tokens[2].Trim()),
 
                            int.Parse(tokens[3].Trim()),
 
                            int.Parse(tokens[4].Trim())
 
                            );
 
                        Reactions.Set(reagent1, reagent2, reaction);
 
                    }
 
                }
 
            }
 

	
 
            if (!File.Exists(_clipFile)) return true;
 
            {
 
                using StreamReader reader = new StreamReader(_clipFile);
 
                using StreamReader reader = new(_clipFile);
 
                while ((line = reader.ReadLine()) != null)
 
                {
 
                    string[] tokens = line.Split(' ');
 
                    
 
                    if (tokens.Length != 3) continue;
 
                    
 
                    string reagent1 = tokens[0].Trim();
 
                    if (!Clippers.ContainsKey(reagent1))
 
                    {
 
                        Clippers.Add(reagent1, new Dictionary<string, ClipType>());
 
                    }
 
                    Clippers[reagent1][tokens[1].Trim()] = (ClipType)int.Parse(tokens[2].Trim());
 
                }
 
            }
 

	
 
            return true;
...
 
@@ -341,56 +341,76 @@ namespace DesertPaintCodex.Models
 
                    {
 
                        if (reagentName1.Equals(reagentName2))
 
                        {
 
                            continue;
 
                        }
 
                        Reagent reagent1 = ReagentService.GetReagent(reagentName1);
 
                        Reagent reagent2 = ReagentService.GetReagent(reagentName2);
 
                        reaction = Reactions.Find(reagent1, reagent2);
 
                        if (reaction != null)
 
                        {
 
                            writer.WriteLine(reagent1.PracticalPaintName + " " + reagent2.PracticalPaintName + " " +
 
                            reaction.Red + " " + reaction.Green + " " + reaction.Blue);
 
                        }
 
                    }
 
                }
 
            }
 
            using (StreamWriter writer = new StreamWriter(_clipFile, false))
 
            using (StreamWriter writer = new(_clipFile, false))
 
            {
 
                foreach (var item1 in Clippers)
 
                {
 
                    foreach (var item2 in item1.Value)
 
                    {
 
                        if (item2.Value == ClipType.None) continue;
 
                        writer.WriteLine(item1.Key + " " + item2.Key + " " + (int)item2.Value);
 
                    }
 
                }
 
            }
 
        }
 

	
 
        public ClipType PairClipStatus(string reagent1, string reagent2)
 
        public ClipType PairClipStatus(Reagent reagent1, Reagent reagent2)
 
        {
 
            if (Clippers.TryGetValue(reagent1, out var item1))
 
            if (Clippers.TryGetValue(reagent1.PracticalPaintName, out var item1))
 
            {
 
                if (item1.TryGetValue(reagent2, out var clipType))
 
                if (item1.TryGetValue(reagent2.PracticalPaintName, out var clipType))
 
                {
 
                    return clipType;
 
                }
 
            }
 
            return ClipType.None;
 
        }
 

	
 
        public void SetPairClipStatus(Reagent reagent1, Reagent reagent2, ClipType clip)
 
        {
 
            if (Clippers.TryGetValue(reagent1.PracticalPaintName, out var item1))
 
            {
 
                if (item1.TryGetValue(reagent2.PracticalPaintName, out var clipType))
 
                {
 
                    if (clipType == clip) return;
 
                }
 
            }
 
            else
 
            {
 
                item1 = new Dictionary<string, ClipType>();
 
                Clippers.Add(reagent1.PracticalPaintName, item1);
 
            }
 

	
 
            item1[reagent2.PracticalPaintName] = clip;
 
            Save();
 
        }
 

	
 
        private void LoadRecipes(Dictionary<string, PaintRecipe> recipeDict, string filename, uint concentration)
 
        {
 
            foreach (PaintRecipe recipe in recipeDict.Values)
 
            {
 
                recipe.Clear();
 
            }
 
            string      recipeFile         = Path.Combine(Directory, filename);
 
            bool        inRecipe           = false;
 
            PaintRecipe testRecipe         = new();
 
            string?     currentRecipeColor = null;
 
            
 
            if (!File.Exists(recipeFile)) return;
 
            
 
            using StreamReader reader = new(recipeFile);
 
            
 
            string? line;
Models/ReactionTest.cs
Show inline comments
...
 
@@ -32,34 +32,34 @@ namespace DesertPaintCodex.Models
 
            {
 
                if (_bufferReagent == value) return;
 
                _bufferReagent = value;
 
                _recipe.Clear();
 
                if (_bufferReagent == null)
 
                {
 
                    _recipe.AddReagent(Reagent1.Name);
 
                    _recipe.AddReagent(Reagent2.Name);
 
                }
 
                else
 
                {
 
                    _recipe.AddReagent(_bufferReagent.Name);
 
                    _recipe.AddReagent(Reagent1.Name);
 
                    _recipe.AddReagent(Reagent2.Name);
 
                }
 
                NotifyPropertyChanged(nameof(BufferReagent));
 
                NotifyPropertyChanged(nameof(HypotheticalColor));
 
                NotifyPropertyChanged(nameof(CanScan));
 
                UpdateHypotheticalColor();
 
            }
 
        }
 

	
 
        private ClipType _clipType;
 
        public ClipType Clipped {
 
            get => _clipType;
 
            set
 
            {
 
                _clipType = value;
 
                NotifyPropertyChanged(nameof(Clipped));
 
            }
 
        }
 

	
 
        public bool IsAllCatalysts { get; }
 

	
 
        private Reaction? _reaction;
...
 
@@ -76,76 +76,80 @@ namespace DesertPaintCodex.Models
 

	
 
        public TestState State
 
        {
 
            get => _state;
 
            set
 
            {
 
                _state = value;
 
                NotifyPropertyChanged(nameof(State));
 
                NotifyPropertyChanged(nameof(Requires3Way));
 
                NotifyPropertyChanged(nameof(CanScan));
 
                NotifyPropertyChanged(nameof(IsScanning));
 
                NotifyPropertyChanged(nameof(HasResults));
 
                NotifyPropertyChanged(nameof(HasReaction));
 
                NotifyPropertyChanged(nameof(CanClear));
 
                NotifyPropertyChanged(nameof(CanSave));
 
                NotifyPropertyChanged(nameof(NoLab));
 
                NotifyPropertyChanged(nameof(CanPickBuffer));
 
            }
 
        }
 

	
 
        public bool Requires3Way =>
 
            (State == TestState.ClippedResult) || IsAllCatalysts;
 

	
 
        public bool CanScan => (State == TestState.Untested) || (State == TestState.LabNotFound) ||
 
            ((State == TestState.ClippedResult) && (BufferReagent != null));
 
        public bool CanScan => (State is TestState.Untested or TestState.LabNotFound) || ((State == TestState.ClippedResult) && (BufferReagent != null));
 
        
 
        public bool IsScanning => State == TestState.Scanning;
 

	
 
        public bool HasResults => State is TestState.ClippedResult or TestState.GoodResult or TestState.LabNotFound;
 
        public bool HasResults => (ObservedColor != null) && (State is TestState.ClippedResult or TestState.GoodResult or TestState.LabNotFound);
 

	
 
        public bool HasReaction => State is TestState.ClippedResult or TestState.GoodResult or TestState.Saved;
 
        
 
        public bool CanClear => State is TestState.ClippedResult or TestState.GoodResult or TestState.Saved;
 
        
 
        public bool CanSave => State == TestState.GoodResult;
 

	
 
        public bool NoLab => State == TestState.LabNotFound;
 
        
 
        public bool CanPickBuffer => (State == TestState.ClippedResult) || IsAllCatalysts;
 

	
 
        public PaintColor? HypotheticalColor => (IsAllCatalysts && (BufferReagent == null)) ? null : _recipe.BaseColor;
 

	
 
        private PaintColor? _hypotheticalColor;
 
        public PaintColor? HypotheticalColor { get => _hypotheticalColor; set { _hypotheticalColor = value; NotifyPropertyChanged(nameof(HypotheticalColor)); } }
 

	
 
        private PaintColor? _observedColor;
 
        public PaintColor? ObservedColor { get => _observedColor; set { _observedColor = value; NotifyPropertyChanged(nameof(ObservedColor)); } }
 

	
 
        private readonly PaintRecipe _recipe = new();
 

	
 
        public bool IsStub { get; }
 

	
 

	
 
        public ReactionTest(Reagent reagent1, Reagent reagent2, Reaction? reaction, ClipType clipType, bool isStub = false)
 
        {
 
            Reagent1 = reagent1;
 
            Reagent2 = reagent2;
 
            IsAllCatalysts = reagent1.IsCatalyst && reagent2.IsCatalyst;
 
            Clipped = clipType;
 
            Reaction = reaction;
 
            State = (reaction != null) ? TestState.Saved :
 
                (clipType == ClipType.None) ? TestState.Untested : TestState.ClippedResult;
 
            _recipe.AddReagent(reagent1.Name);
 
            _recipe.AddReagent(reagent2.Name);
 
            IsStub = isStub;
 
            UpdateHypotheticalColor();
 
        }
 

	
 
        
 
        #region Actions
 
        public async Task StartScan()
 
        {
 
            Clipped = ClipType.None;
 
            ScanProgress = 0;
 
            Reaction = null;
 
            BadReaction = null;
 
            State = TestState.Scanning;
 
            bool foundLab = await ReactionScannerService.Instance.CaptureReactionAsync(this);
 
            if (foundLab)
 
            {
 
                ObservedColor = ReactionScannerService.Instance.RecordedColor;
 
                if (_observedColor != null)
...
 
@@ -160,104 +164,112 @@ namespace DesertPaintCodex.Models
 
                        {
 
                            0   => ClipType.GreenLow,
 
                            255 => ClipType.GreenHigh,
 
                            _   => ClipType.None
 
                        }
 
                        | _observedColor.Blue switch
 
                        {
 
                            0   => ClipType.BlueLow,
 
                            255 => ClipType.BlueHigh,
 
                            _   => ClipType.None
 
                        };
 

	
 
                    if (Clipped == ClipType.None)
 
                    {
 
                        State = TestState.GoodResult;
 
                        Reaction = CalculateReaction();
 

	
 
                    }
 
                    else
 
                    {
 
                        State = TestState.ClippedResult;
 
                        BadReaction = CalculateReaction();
 
                    }
 
                    
 
                    PlayerProfile? profile = ProfileManager.CurrentProfile;
 
                    profile?.SetPairClipStatus(Reagent1, Reagent2, Clipped);
 
                }
 
            }
 
            else
 
            {
 
                Debug.WriteLine("ERROR: Lab UI not found.");
 
                State = TestState.LabNotFound;
 
            }
 
        }
 

	
 
        public void CancelScan()
 
        {
 
            ReactionScannerService.Instance.CancelScan();
 
            State = TestState.Untested;
 
        }
 

	
 
        public void MarkInert()
 
        {
 
            Reaction = new Reaction(0, 0, 0);
 
            State = TestState.GoodResult;
 
        }
 

	
 
        public void ClearReaction()
 
        {
 
            PlayerProfile? profile = ProfileManager.CurrentProfile;
 
            if (profile == null) return;
 
            profile.Reactions.Remove(Reagent1, Reagent2);
 
            profile.SetPairClipStatus(Reagent1, Reagent2, ClipType.None);
 
            if (State == TestState.Saved)
 
            {
 
                profile.Save();
 
            }
 
            
 
            Reaction = null;
 
            BadReaction = null;
 
            State = TestState.Untested;
 
        }
 

	
 
        public void SaveReaction()
 
        {
 
            PlayerProfile? profile = ProfileManager.CurrentProfile;
 
            if (profile == null) return;
 
            profile.Reactions.Set(Reagent1, Reagent2, Reaction);
 
            profile.Save();
 
            State = TestState.Saved;
 
        }
 
        
 
        #endregion
 
        
 
        #region Internals
 

	
 
        private Reaction? CalculateReaction()
 
        {
 
            if (ProfileManager.CurrentProfile == null) return null;
 
            if (HypotheticalColor == null) return null;
 
            if (ObservedColor == null) return null;
 

	
 
            if (Requires3Way)
 
            if (BufferReagent != null)
 
            {
 
                if (BufferReagent == null) return null;
 
                return ReactionScannerService.Calculate3WayReaction(ProfileManager.CurrentProfile, HypotheticalColor,
 
                    ObservedColor, BufferReagent, Reagent1, Reagent2);
 
            }
 
            return ReactionScannerService.CalculateReaction(HypotheticalColor, ObservedColor);
 
        }
 
        
 
        
 
        private void UpdateHypotheticalColor()
 
        {
 
            HypotheticalColor = null;
 
            HypotheticalColor = (IsAllCatalysts && (BufferReagent == null)) ? null : _recipe.BaseColor;
 
        }
 
        
 
        #endregion
 
        
 
        
 

	
 
        #region Interface Implementations
 
        
 
        public event PropertyChangedEventHandler? PropertyChanged;
 

	
 
        [NotifyPropertyChangedInvocator]
 
        private void NotifyPropertyChanged([CallerMemberName] string? propertyName = null)
 
        {
 
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        }
 

	
 
        public void Report(float value)
 
        {
Models/ReactionTestService.cs
Show inline comments
...
 
@@ -19,33 +19,33 @@ namespace DesertPaintCodex.Models
 
        public static void Initialize()
 
        {
 
            _allTests.Clear();
 
            PlayerProfile? profile = ProfileManager.CurrentProfile;
 
            
 
            Debug.Assert(profile != null);
 
            
 
            List<string> reagentNames = ReagentService.Names;
 

	
 
            foreach (Reagent reagent1 in reagentNames.Select(ReagentService.GetReagent))
 
            {
 
                foreach (Reagent reagent2 in reagentNames.Select(ReagentService.GetReagent))
 
                {
 
                    if (reagent1 == reagent2) continue;
 

	
 
                    Reaction? reaction = profile.FindReaction(reagent1, reagent2);
 
                    ClipType clipType = profile.PairClipStatus(reagent1.Name, reagent2.Name);
 
                    ClipType clipType = profile.PairClipStatus(reagent1, reagent2);
 
                    
 
                    ReactionTest test = new(reagent1, reagent2, reaction, clipType)
 
                    {
 
                        Clipped  = clipType,
 
                        Reaction = reaction,
 
                       
 
                    };
 

	
 
                    _allTests.Add(test);
 
                }
 
            }
 
        }
 

	
 
        public static void PopulateRemainingTests(ObservableCollection<ReactionTest> collection)
 
        {
 
            collection.Clear();
ViewModels/ReactionTestViewModel.cs
Show inline comments
 
using System;
 
using System.Collections.Generic;
 
using System.Collections.ObjectModel;
 
using System.Diagnostics;
 
using System.Linq;
 
using System.Reactive;
 
using System.Reactive.Linq;
 
using System.Threading.Tasks;
 
using DesertPaintCodex.Models;
 
using DesertPaintCodex.Services;
 
using DesertPaintCodex.Util;
 
using ReactiveUI;
 
using DynamicData;
 

	
 
namespace DesertPaintCodex.ViewModels
 
{
 
    public class ReactionTestViewModel : ViewModelBase
 
    {
 
        private ReactionTest _reactionTest = Constants.StubReactionTest;
 
        public ReactionTest ReactionTest
 
        {
 
            get => _reactionTest;
 
            set => this.RaiseAndSetIfChanged(ref _reactionTest, value);
 
        }
 

	
 
        private readonly List<Reagent> _allPigmentList = new();
 
        public ObservableCollection<Reagent> BufferPigmentList { get; } = new();
 
        private readonly List<Reagent> _allReagentList = new();
 
        public ObservableCollection<Reagent> BufferList { get; } = new();
 

	
 

	
 
        public ReactionTestViewModel()
 
        {
 
            List<string> reagentNames = ReagentService.Names;
 
            
 
            foreach (var reagent in reagentNames.Select(ReagentService.GetReagent).Where(x => !x.IsCatalyst))
 
            foreach (var reagent in reagentNames.Select(ReagentService.GetReagent))
 
            {
 
                if (!reagent.IsCatalyst)
 
            {
 
                _allPigmentList.Add(reagent);
 
            }
 
                _allReagentList.Add(reagent);
 
            }
 

	
 
            this.WhenAnyValue(x => x.ReactionTest)
 
                .Subscribe(_ => UpdateDerivedState());
 
            
 
            ShowScreenSettingsDialog = new Interaction<ScreenSettingsViewModel, Unit>();
 
            SaveReaction = ReactiveCommand.Create(() => ReactionTest.SaveReaction());
 
            ClearReaction = ReactiveCommand.Create(() => ReactionTest.ClearReaction());
 
            FinalizeTestResults = ReactiveCommand.Create(Test);
 
        }
 

	
 
        private void UpdateDerivedState()
 
        {
 
            // There are more "reactive" ways to pull this off, but I don't have time to figure out all those recipes
 
            // right now.
 
            SetupTest();
 
        }
 
            
 
        
 
        private void SetupTest()
 
        {
 
            if (ReactionTest.Requires3Way)
 
            {
 
                FilterBufferPigments();
 
            }
 
        }
 

	
 
        private void FilterBufferPigments()
 
        {
 
            // Avalonia doesn't really have proper filtering for listy controls yet.
 
            BufferPigmentList.Clear();
 
            PlayerProfile? profile = ProfileManager.CurrentProfile;
 
            if (profile == null) return;
 
            ReactionSet reactions = profile.Reactions;
 
            BufferList.Clear();
 
            List<Reagent> bufferList = ReactionTest.IsAllCatalysts ? _allPigmentList : _allReagentList;
 
            foreach (Reagent pigment in bufferList)
 
            {
 
                if (pigment == ReactionTest.Reagent1) continue;
 
                if (pigment == ReactionTest.Reagent2) continue;
 
                if (reactions.Find(pigment, ReactionTest.Reagent1) == null) continue;
 
                if (reactions.Find(pigment, ReactionTest.Reagent2) == null) continue;
 

	
 
            BufferPigmentList.AddRange(_allPigmentList.Where(pigment =>
 
                    pigment != ReactionTest.Reagent1 && pigment != ReactionTest.Reagent2));
 
                BufferList.Add(pigment);
 
            }
 

	
 
            // BufferPigmentList.AddRange(_allPigmentList.Where(pigment =>
 
            //         pigment != ReactionTest.Reagent1 && pigment != ReactionTest.Reagent2));
 
        }
 

	
 
        public async void Analyze()
 
        {
 
            Debug.WriteLine("Analyze");
 
            try
 
            {
 
                await ReactionTest.StartScan();
 
                await FinalizeTestResults.Execute();
 
            }
 
            catch (OperationCanceledException)
 
            {
 
                Debug.WriteLine("Scan canceled.");
 
            }
 
        }
 

	
Views/MainWindow.axaml
Show inline comments
 
<Window 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"
 
        mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="800"
 
        Width="640" Height="800"
 
        MinWidth="600" MinHeight="500"
 
        Topmost="True"
 
        x:Class="DesertPaintCodex.Views.MainWindow"
 
        Icon="/Assets/desert_paint_codex_icon.ico"
 
        Title="Desert Paint Codex">
 

	
 
    <Design.DataContext>
 
        <vm:MainWindowViewModel/>
 
    </Design.DataContext>
 
    
 
    <Window.DataContext>
 
        <vm:MainWindowViewModel/>
 
    </Window.DataContext>
 
    
 
    <Window.Styles>
 
        <!--
 
        <Style Selector="Menu">
 
            <Setter Property="Background" Value="#282828"/>
Views/ReactionTestView.axaml
Show inline comments
...
 
@@ -26,35 +26,35 @@
 
            <Setter Property="Width" Value="80"/>
 
        </Style>
 
        
 
        <Style Selector="TextBlock.ReagentName">
 
            <Setter Property="VerticalAlignment" Value="Center"/>
 
            <Setter Property="Width" Value="120"/>
 
        </Style>
 
    </UserControl.Styles>
 

	
 
    <StackPanel Spacing="20" Margin="20 0 0 0">
 
        <StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="5">
 
            <TextBlock Classes="BlockHeader">REAGENTS</TextBlock>
 
            <Border Classes="ThinFrame">
 
                <StackPanel Orientation="Vertical" Spacing="10">
 
                    <StackPanel Classes="ReagentRow" IsVisible="{Binding ReactionTest.Requires3Way}">
 
                        <TextBlock Classes="ReagentLabel">Buffer:</TextBlock>
 
                        <TextBlock Classes="ReagentName" Text="{Binding ReactionTest.BufferReagent.Name, FallbackValue=[Unknown]}" IsVisible="{Binding !ReactionTest.CanScan}"/>
 
                        <ComboBox Items="{Binding BufferPigmentList}"
 
                                  IsVisible="{Binding ReactionTest.CanScan}"
 
                        <TextBlock Classes="ReagentName" Text="{Binding ReactionTest.BufferReagent.Name, FallbackValue=[Unknown]}" IsVisible="{Binding !ReactionTest.CanPickBuffer}"/>
 
                        <ComboBox Items="{Binding BufferList}"
 
                                  IsVisible="{Binding ReactionTest.CanPickBuffer}"
 
                                  SelectedItem="{Binding ReactionTest.BufferReagent}"
 
                                  PlaceholderText="Choose" Width="120" Height="30">
 
                            <ComboBox.ItemTemplate>
 
                                <DataTemplate>
 
                                    <TextBlock Text="{Binding Name}" />
 
                                </DataTemplate>
 
                            </ComboBox.ItemTemplate>
 
                        </ComboBox>
 
                        <Border Classes="ReagentSwatch"
 
                            Background="{Binding ReactionTest.BufferReagent.Color, Converter={StaticResource paintToBrush}, FallbackValue=#00000000}" />
 
                    </StackPanel>
 
                    <StackPanel Classes="ReagentRow">
 
                        <TextBlock Classes="ReagentLabel">Reagent #1:</TextBlock>
 
                        <TextBlock Classes="ReagentName" Text="{Binding ReactionTest.Reagent1.Name}" />
 
                        <Border Classes="ReagentSwatch"
 
                            Background="{Binding ReactionTest.Reagent1.Color, Converter={StaticResource paintToBrush}, FallbackValue=#00000000}" />
0 comments (0 inline, 0 general)