Compare commits

...
31 Commits
Author SHA1 Message Date
Alexandru Macocian 6bd9877925 Remove dependency on InputSimulator. 2021-05-03 16:23:30 +02:00
Alexandru Macocian d38df6596f Remove keyboard macro functionality for now.
Unify input prompts.
Modify screenshot manager functionality to apply functionality of settings changed.
Update to 0.8.6.
2021-05-03 16:16:00 +02:00
Alexandru Macocian b4e31e859c Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-05-02 18:49:31 +02:00
Alexandru Macocian 1acd07c1be Change the way shortcut manager manages shortcuts.
Base for keyboard macros.
2021-05-02 18:49:25 +02:00
amacocianandGitHub b9b25bb098 Update README.md 2021-04-27 17:28:25 +02:00
amacocianandGitHub 5ea2981dc7 Update README.md 2021-04-27 17:25:03 +02:00
Alexandru Macocian 4fb96ca3a4 Ability to place screenshots in desired location. 2021-04-27 17:06:28 +02:00
Alexandru Macocian 6de52f5585 Ability to test placement while guildwars is running. 2021-04-26 11:26:43 +02:00
Alexandru Macocian 6622e41f64 Wait for guildwars window to be initialized before moving to desired window. 2021-04-26 11:02:23 +02:00
Alexandru Macocian 8405e4d1e5 Return to settings category after saving desired window. 2021-04-25 18:26:29 +02:00
Alexandru Macocian 0edc3f1560 Throw exception when screen id is invalid. 2021-04-25 18:24:51 +02:00
Alexandru Macocian c109fe7561 Ability to select on which window to place guildwars. 2021-04-25 18:20:45 +02:00
Alexandru Macocian b5413dca5c Include [Admin] tag when running as administrator. 2021-04-24 01:35:46 +02:00
Alexandru Macocian 6c2d4a1e70 No longer require administrator rights for normal usage.
Request elevation only in necesarry parts of the code.
Request elevation on update.
After update, launch application without elevation.
2021-04-24 01:20:36 +02:00
Alexandru Macocian be03f1a32d Run updates as admin 2021-04-23 23:37:01 +02:00
Alexandru Macocian 3ed1cc453a Implemented functionality to launch guildwars with lower privilege. 2021-04-23 20:24:28 +02:00
Alexandru Macocian 16030063ed Fix skill icon not unloading.
Moved all help icons on same side.
2021-04-23 19:52:09 +02:00
Alexandru Macocian 458696ad8f Fixed a bug where skill templates would try to load the image for No Skill.
Fixed a bug where build template manager would delete saved template.
2021-04-23 17:28:12 +02:00
Alexandru Macocian 6201702563 Skill holder now properly shows the top half of icons in the case where icons are bigger than normal. 2021-04-23 17:14:24 +02:00
Alexandru Macocian c5bea23a8f Make professions and attributes searchable.
Minor QOL changes to build view.
Handle exception caused by browser initialization getting canceled.
2021-04-23 16:59:31 +02:00
Alexandru Macocian e5f38f77ef Reverted html agility pack. 2021-04-23 11:40:05 +02:00
Alexandru Macocian 1d774ab8de Disable script injection when dynamic build loading is disabled.
Fixed a bug where earth prayers was not a recognized attribute.
2021-04-23 11:11:19 +02:00
Alexandru Macocian bc66ca9948 Feature flag to disable dynamic build loading in experimental settings. 2021-04-22 17:53:04 +02:00
amacocianandGitHub 524cd21fd4 Update README.md 2021-04-22 17:39:51 +02:00
amacocianandGitHub 132cb0e1f4 Update README.md 2021-04-22 17:32:35 +02:00
amacocianandGitHub 09c99e7731 Update README.md 2021-04-22 17:29:49 +02:00
Alexandru Macocian c9d0be1117 Update to 0.7.0 2021-04-22 17:02:48 +02:00
Alexandru Macocian 965055c8d7 Build management implementation.
Load build from browser when selection is detected to be a build template.
Load skill images from internet on demand.
Load skill details page when a skill is selected.
Separate skills on professions.
2021-04-22 17:02:28 +02:00
Alexandru Macocian f6bb21d0ef Build template parser and encoder.
Unit test project.
2021-04-20 20:06:15 +02:00
Alexandru Macocian 465e0a4ce0 Fix buttons while browser is fullscreen. 2021-04-20 12:51:26 +02:00
Alexandru Macocian 55f304b885 Update to wpfextended 0.2 2021-04-19 21:37:15 +02:00
130 changed files with 7613 additions and 223 deletions
+22
View File
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.0.0-alpha0002" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Daybreak\Daybreak.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,96 @@
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Logging;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Collections.Generic;
namespace Daybreak.Tests.Services
{
[TestClass]
public class BuildTemplateManagerTests
{
private const string EncodedTemplate = "OwBk0texXNu0Dj/z+TDzBj+TN4AE";
private IBuildTemplateManager buildTemplateManager;
[TestInitialize]
public void Initialize()
{
buildTemplateManager = new BuildTemplateManager(new Mock<ILogger>().Object);
}
[TestMethod]
public void TestDecode()
{
var build = this.buildTemplateManager.DecodeTemplate(EncodedTemplate);
build.Primary.Should().Be(Profession.Assassin);
build.Secondary.Should().Be(Profession.None);
build.Attributes.Count.Should().Be(4);
build.Attributes[0].Attribute.Should().Be(Attribute.DaggerMastery);
build.Attributes[0].Points.Should().Be(11);
build.Attributes[1].Attribute.Should().Be(Attribute.DeadlyArts);
build.Attributes[1].Points.Should().Be(1);
build.Attributes[2].Attribute.Should().Be(Attribute.ShadowArts);
build.Attributes[2].Points.Should().Be(5);
build.Attributes[3].Attribute.Should().Be(Attribute.CriticalStrikes);
build.Attributes[3].Points.Should().Be(11);
build.Skills.Count.Should().Be(8);
build.Skills[0].Should().Be(Skill.UnsuspectingStrike);
build.Skills[1].Should().Be(Skill.WildStrike);
build.Skills[2].Should().Be(Skill.CriticalStrike);
build.Skills[3].Should().Be(Skill.MoebiusStrike);
build.Skills[4].Should().Be(Skill.DeathBlossom);
build.Skills[5].Should().Be(Skill.CriticalEye);
build.Skills[6].Should().Be(Skill.CriticalAgility);
build.Skills[7].Should().Be(Skill.CriticalDefenses);
}
[TestMethod]
public void TestEncode()
{
var build = new Build()
{
Primary = Profession.Assassin,
Secondary = Profession.None,
Attributes = new List<AttributeEntry>
{
new AttributeEntry
{
Attribute = Attribute.DaggerMastery,
Points = 11
},
new AttributeEntry
{
Attribute = Attribute.DeadlyArts,
Points = 1
},
new AttributeEntry
{
Attribute = Attribute.ShadowArts,
Points = 5
},
new AttributeEntry
{
Attribute = Attribute.CriticalStrikes,
Points = 11
}
},
Skills = new List<Skill>
{
Skill.UnsuspectingStrike,
Skill.WildStrike,
Skill.CriticalStrike,
Skill.MoebiusStrike,
Skill.DeathBlossom,
Skill.CriticalEye,
Skill.CriticalAgility,
Skill.CriticalDefenses
}
};
var encoded = this.buildTemplateManager.EncodeTemplate(build);
encoded.Should().Be(EncodedTemplate);
}
}
}
+10
View File
@@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.31005.135
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak", "Daybreak\Daybreak.csproj", "{AA45C2B1-8BD0-466C-9271-699F168905AF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Daybreak.Tests", "Daybreak.Tests\Daybreak.Tests.csproj", "{C911C1C2-6566-419C-89C7-F802EB3FE709}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,14 @@ Global
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|Any CPU.Build.0 = Release|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|x64.ActiveCfg = Release|x64
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|x64.Build.0 = Release|x64
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Debug|x64.ActiveCfg = Debug|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Debug|x64.Build.0 = Debug|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|Any CPU.Build.0 = Release|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|x64.ActiveCfg = Release|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+55
View File
@@ -0,0 +1,55 @@
using System;
using System.Extensions;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace Daybreak.Behaviors
{
public class ScrollIntoView : Behavior<ListView>
{
/// <summary>
/// When Beahvior is attached
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
/// <summary>
/// On Selection Changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void AssociatedObject_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (sender is ListView)
{
var listView = sender.As<ListView>();
if (listView.SelectedItem != null)
{
listView.Dispatcher.BeginInvoke(
(Action)(() =>
{
listView.UpdateLayout();
if (listView.SelectedItem !=
null)
listView.ScrollIntoView(
listView.SelectedItem);
}));
}
}
}
/// <summary>
/// When behavior is detached
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -=
AssociatedObject_SelectionChanged;
}
}
}
@@ -1,11 +1,16 @@
using Daybreak.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace Daybreak.Configuration
{
public sealed class ApplicationConfiguration
{
[JsonProperty("SetGuildwarsWindowSizeOnLaunch")]
public bool SetGuildwarsWindowSizeOnLaunch { get; set; }
[JsonProperty("DesiredGuildwarsScreen")]
public int DesiredGuildwarsScreen { get; set; }
[JsonProperty("BrowsersEnabled")]
public bool BrowsersEnabled { get; set; } = true;
[JsonProperty("ToolboxPath")]
@@ -26,5 +31,9 @@ namespace Daybreak.Configuration
public bool AddressBarReadonly { get; set; } = true;
[JsonProperty("ExperimentalFeatures")]
public ExperimentalFeatures ExperimentalFeatures { get; set; } = new();
[JsonProperty("ShortcutLocation")]
public string ShortcutLocation { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
[JsonProperty("PlaceShortcut")]
public bool PlaceShortcut { get; set; }
}
}
+11 -1
View File
@@ -1,4 +1,6 @@
using Newtonsoft.Json;
using Daybreak.Models;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Daybreak.Configuration
{
@@ -8,5 +10,13 @@ namespace Daybreak.Configuration
public bool MultiLaunchSupport { get; set; }
[JsonProperty("ToolboxAutoLaunchDelay")]
public int ToolboxAutoLaunchDelay { get; set; } = 5000;
[JsonProperty("DynamicBuildLoading")]
public bool DynamicBuildLoading { get; set; } = true;
[JsonProperty("LaunchGuildwarsAsCurrentUser")]
public bool LaunchGuildwarsAsCurrentUser { get; set; } = true;
[JsonProperty("CanInterceptKeys")]
public bool CanInterceptKeys { get; set; }
[JsonProperty("Macros")]
public List<KeyMacro> Macros { get; set; } = new();
}
}
@@ -1,12 +1,17 @@
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Services.Bloogum;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
using Daybreak.Services.Screens;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Shortcuts;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
@@ -33,6 +38,11 @@ namespace Daybreak.Configuration
serviceProducer.RegisterSingleton<IApplicationUpdater, ApplicationUpdater>();
serviceProducer.RegisterSingleton<IMutexHandler, MutexHandler>();
serviceProducer.RegisterSingleton<IRuntimeStore, RuntimeStore>();
serviceProducer.RegisterSingleton<IBuildTemplateManager, BuildTemplateManager>();
serviceProducer.RegisterSingleton<IIconRetriever, IconRetriever>();
serviceProducer.RegisterSingleton<IPrivilegeManager, PrivilegeManager>();
serviceProducer.RegisterSingleton<IScreenManager, ScreenManager>();
serviceProducer.RegisterSingleton<IShortcutManager, ShortcutManager>();
}
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
@@ -41,6 +51,7 @@ namespace Daybreak.Configuration
applicationLifetimeProducer.RegisterService<ILoggingDatabase>();
applicationLifetimeProducer.RegisterService<IScreenshotProvider>();
applicationLifetimeProducer.RegisterService<IApplicationUpdater>();
applicationLifetimeProducer.RegisterService<IShortcutManager>();
}
public static void RegisterViews(IViewProducer viewProducer)
{
@@ -54,6 +65,10 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<AccountsView>();
viewProducer.RegisterView<ExperimentalSettingsView>();
viewProducer.RegisterView<ExecutablesView>();
viewProducer.RegisterView<BuildTemplateView>();
viewProducer.RegisterView<BuildsListView>();
viewProducer.RegisterView<RequestElevationView>();
viewProducer.RegisterView<ScreenChoiceView>();
}
}
}
+19
View File
@@ -0,0 +1,19 @@
<UserControl x:Class="Daybreak.Controls.HelpButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Grid>
<Ellipse x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Visibility="Visible" Opacity="0.1" Width="30" Height="30" />
<Ellipse Width="30" Height="30" StrokeThickness="2" Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Ellipse>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Center"
Data="m4.99353,17.899l0,-1.47c0,-1.436 0.322,-2.188 1.075,-3.229l2.404,-3.3c1.254,-1.721 1.684,-2.546 1.684,-3.766c0,-2.044 -1.434,-3.335 -3.479,-3.335c-2.008,0 -3.299,1.219 -3.729,3.407c-0.036,0.215 -0.179,0.323 -0.395,0.287l-2.259,-0.395c-0.216,-0.036 -0.323,-0.179 -0.288,-0.395c0.539,-3.443 3.014,-5.703 6.744,-5.703c3.872,0 6.49,2.546 6.49,6.097c0,1.722 -0.608,2.977 -1.828,4.663l-2.403,3.3c-0.717,0.968 -0.933,1.47 -0.933,2.689l0,1.147c0,0.215 -0.143,0.358 -0.358,0.358l-2.367,0c-0.215,0.004 -0.358,-0.14 -0.358,-0.355zm-0.179,3.444c0,-0.215 0.143,-0.358 0.359,-0.358l2.726,0c0.215,0 0.358,0.144 0.358,0.358l0,3.084c0,0.216 -0.144,0.358 -0.358,0.358l-2.726,0c-0.217,0 -0.359,-0.143 -0.359,-0.358l0,-3.084z"></Path>
<Ellipse Fill="Transparent" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"></Ellipse>
</Grid>
</Viewbox>
</UserControl>
@@ -0,0 +1,37 @@
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for AddButton.xaml
/// </summary>
public partial class HelpButton : UserControl
{
public event EventHandler Clicked;
public HelpButton()
{
InitializeComponent();
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0.6;
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0;
}
private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
this.Clicked?.Invoke(this, e);
}
}
}
}
@@ -0,0 +1,20 @@
<UserControl x:Class="Daybreak.Controls.MinusButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Grid>
<Ellipse x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Visibility="Visible" Opacity="0.1" />
<Path Data="m13,26a13,13 0 1 1 13,-13a13,13 0 0 1 -13,13zm0,-24a11,11 0 1 0 11,11a11,11 0 0 0 -11,-11z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Path>
<Path Data="m19,14l-12,0a1,1 0 0 1 0,-2l12,0a1,1 0 0 1 0,2z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Path>
<Ellipse Fill="Transparent" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"></Ellipse>
</Grid>
</Viewbox>
</UserControl>
@@ -0,0 +1,37 @@
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for AddButton.xaml
/// </summary>
public partial class MinusButton : UserControl
{
public event EventHandler Clicked;
public MinusButton()
{
InitializeComponent();
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0.6;
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0;
}
private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
Clicked?.Invoke(this, e);
}
}
}
}
+14 -1
View File
@@ -14,6 +14,18 @@
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" TriggerValue="True"></converters:BooleanToVisibilityConverter>
<converters:BooleanToVisibilityConverter x:Key="ReverseBooleanToVisibilityConverter" TriggerValue="False"></converters:BooleanToVisibilityConverter>
</UserControl.Resources>
<UserControl.ContextMenu>
<ContextMenu>
<ContextMenu.Template>
<ControlTemplate>
<StackPanel Margin="10">
<local:OpaqueButton Text="Load build template" Foreground="White" Background="#F0202020" BackgroundOpacity="0.4"
Clicked="LoadBuildTemplateButton_Click" FontSize="16" Height="40" Width="200"></local:OpaqueButton>
</StackPanel>
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
@@ -21,7 +33,8 @@
</Grid.RowDefinitions>
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></wv2:WebView2>
<Grid Grid.Row="1" Background="#80808080" IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}">
<Grid Grid.Row="1" Background="#80808080" IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
Visibility="{Binding ElementName=_this, Path=ControlsEnabled, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
@@ -1,4 +1,7 @@
using Daybreak.Launch;
using Daybreak.Models.Browser;
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Services.Logging;
using Daybreak.Utils;
@@ -26,14 +29,29 @@ namespace Daybreak.Controls
public readonly static DependencyProperty BrowserEnabledProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(BrowserEnabled));
public readonly static DependencyProperty AddressBarReadonlyProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(AddressBarReadonly));
public readonly static DependencyProperty BrowserSupportedProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(BrowserSupported), new PropertyMetadata(true));
public readonly static DependencyProperty ControlsEnabledProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(ControlsEnabled), new PropertyMetadata(true));
public readonly static DependencyProperty CanNavigateProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(CanNavigate), new PropertyMetadata(true));
public readonly static DependencyProperty CanDownloadBuildProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(CanDownloadBuild), new PropertyMetadata(false));
public event EventHandler<string> FavoriteUriChanged;
public event EventHandler MaximizeClicked;
public event EventHandler<Build> BuildDecoded;
private readonly IConfigurationManager configurationManager;
private readonly ILogger logger;
private readonly IBuildTemplateManager buildTemplateManager;
private CoreWebView2Environment coreWebView2Environment;
public bool CanDownloadBuild
{
get => this.GetTypedValue<bool>(CanDownloadBuildProperty);
set => this.SetTypedValue<bool>(CanDownloadBuildProperty, value);
}
public bool CanNavigate
{
get => this.GetTypedValue<bool>(CanNavigateProperty);
set => this.SetTypedValue<bool>(CanNavigateProperty, value);
}
public string Address
{
get => this.GetTypedValue<string>(AddressProperty);
@@ -52,7 +70,7 @@ namespace Daybreak.Controls
public bool AddressBarReadonly
{
get => this.GetTypedValue<bool>(AddressBarReadonlyProperty);
private set => this.SetTypedValue<bool>(AddressBarReadonlyProperty, value);
set => this.SetTypedValue<bool>(AddressBarReadonlyProperty, value);
}
public bool BrowserEnabled
{
@@ -64,11 +82,17 @@ namespace Daybreak.Controls
get => this.GetTypedValue<bool>(BrowserSupportedProperty);
private set => this.SetTypedValue<bool>(BrowserSupportedProperty, value);
}
public bool ControlsEnabled
{
get => this.GetTypedValue<bool>(ControlsEnabledProperty);
set => this.SetTypedValue<bool>(ControlsEnabledProperty, value);
}
public ChromiumBrowserWrapper()
{
this.configurationManager = Launcher.ApplicationServiceManager.GetService<IConfigurationManager>();
this.logger = Launcher.ApplicationServiceManager.GetService<ILogger>();
this.buildTemplateManager = Launcher.ApplicationServiceManager.GetService<IBuildTemplateManager>();
this.InitializeComponent();
this.InitializeEnvironment();
this.InitializeBrowser();
@@ -114,9 +138,27 @@ namespace Daybreak.Controls
{
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
this.CanDownloadBuild = this.configurationManager.GetConfiguration().ExperimentalFeatures.DynamicBuildLoading;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.NavigationStarting += (browser, args) => this.Navigating = true;
this.WebBrowser.NavigationStarting += (browser, args) =>
{
if (this.CanNavigate is false && args.Uri != this.Address)
{
args.Cancel = true;
}
else
{
this.Navigating = true;
}
};
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
this.WebBrowser.WebMessageReceived += this.CoreWebView2_WebMessageReceived;
this.WebBrowser.CoreWebView2.Settings.AreDevToolsEnabled = false;
this.WebBrowser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
if (this.CanDownloadBuild)
{
await this.WebBrowser.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(Scripts.SendSelectionOnContextMenu);
}
}
}
@@ -158,9 +200,60 @@ namespace Daybreak.Controls
}
}
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs args)
{
BrowserPayload payload = default;
try
{
payload = args.WebMessageAsJson.Deserialize<BrowserPayload>();
}
catch(Exception e)
{
this.logger.LogError(e);
}
if (payload?.Key == BrowserPayload.PayloadKeys.ContextMenu)
{
var contextMenuPayload = args.WebMessageAsJson.Deserialize<BrowserPayload<OnContextMenuPayload>>();
var maybeTemplate = contextMenuPayload.Value.Selection;
if (string.IsNullOrWhiteSpace(maybeTemplate))
{
return;
}
if (this.buildTemplateManager.IsTemplate(maybeTemplate) is false)
{
return;
}
Task.Run(() =>
{
try
{
var build = this.buildTemplateManager.DecodeTemplate(maybeTemplate);
this.Dispatcher.Invoke(() =>
{
this.ContextMenu.DataContext = build;
this.ContextMenu.IsOpen = true;
});
}
catch(Exception e)
{
this.logger.LogWarning($"Exception when decoding template {maybeTemplate}. Details {e}");
}
});
}
}
private void LoadBuildTemplateButton_Click(object sender, EventArgs e)
{
var build = this.ContextMenu.DataContext.As<Build>();
this.ContextMenu.IsOpen = false;
this.BuildDecoded?.Invoke(this, build);
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
this.WebBrowser.Dispose();
this.WebBrowser?.Dispose();
}
private void BackButton_Clicked(object sender, EventArgs e)
@@ -0,0 +1,553 @@
<UserControl x:Class="Daybreak.Controls.CircularLoadingWidget"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Daybreak.Controls"
Height="Auto" Width="Auto">
<UserControl.Resources>
<Storyboard x:Key="ProgressAnimation" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#00000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block17" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="Black"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block16" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#EF000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block15" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#E2000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block14" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#D3000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block13" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#C6000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block12" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#B7000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block11" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#AA000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block10" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#23000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#9B000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block9" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#91000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block8" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#91000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#7F000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block7" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#91000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#72000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block6" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#91000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#63000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block5" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#91000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#56000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block4" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#91000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#3D000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block3" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#91000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#26000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block2" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#0C000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#91000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#19000000"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="block1" Storyboard.TargetProperty="(UIElement.OpacityMask).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#00000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.2290000" Value="Black"/>
<SplineColorKeyFrame KeyTime="00:00:00.4590000" Value="#EF000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.6880000" Value="#E2000000"/>
<SplineColorKeyFrame KeyTime="00:00:00.9180000" Value="#D3000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.1470000" Value="#C6000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.3760000" Value="#B7000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.6060000" Value="#AA000000"/>
<SplineColorKeyFrame KeyTime="00:00:01.8350000" Value="#9B000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.0650000" Value="#8E000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.2940000" Value="#7F000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.5240000" Value="#72000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.7530000" Value="#63000000"/>
<SplineColorKeyFrame KeyTime="00:00:02.9820000" Value="#56000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.2120000" Value="#3D000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.4410000" Value="#26000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.6710000" Value="#19000000"/>
<SplineColorKeyFrame KeyTime="00:00:03.9000000" Value="#0C000000"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard x:Name="ProgressAnimation_BeginStoryboard" Storyboard="{StaticResource ProgressAnimation}"/>
</EventTrigger>
</UserControl.Triggers>
<Viewbox>
<Canvas x:Name="LayoutRoot" VerticalAlignment="Top" Height="88" Width="88">
<Grid Width="10.734" Height="10.004" Canvas.Left="38.614" Canvas.Top="0.331">
<Rectangle Fill="White" x:Name="block" RenderTransformOrigin="0.5,4.3689" OpacityMask="#00000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="180"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block1" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#0C000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="-160"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block2" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#19000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-140"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block3" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#26000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="-119.99999999999999"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block4" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#3D000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="-100"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block5" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#56000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="-80"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block6" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#64000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-59.999999999999993"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block7" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#72000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="-40"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block8" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#80000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-19.999999999999996"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block9" Fill="White" OpacityMask="#8E000000" RenderTransformOrigin="0.5,4.3689" VerticalAlignment="Top" Height="10.004"/>
<Rectangle x:Name="block10" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#9C000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="19.999999999999996"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block11" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#AA000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="40"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block12" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#B8000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="59.999999999999993"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block13" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#C6000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="80"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block14" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#D4000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="100"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block15" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#E2000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="119.99999999999999"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block16" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="#F0000000" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="140"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="block17" Fill="White" RenderTransformOrigin="0.5,4.3689" OpacityMask="Black" VerticalAlignment="Top" Height="10.004">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.99999999999999989" ScaleY="0.99999999999999989"/>
<SkewTransform/>
<RotateTransform Angle="160"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Canvas>
</Viewbox>
</UserControl>
@@ -0,0 +1,32 @@
using System.Windows;
using System.Windows.Controls;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for CircularLoadingWidget.xaml
/// </summary>
public partial class CircularLoadingWidget : UserControl
{
public CircularLoadingWidget()
{
InitializeComponent();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == IsEnabledProperty)
{
if (e.NewValue is true)
{
this.ProgressAnimation_BeginStoryboard.Storyboard.Pause();
}
else
{
this.ProgressAnimation_BeginStoryboard.Storyboard.Resume();
}
}
}
}
}
@@ -0,0 +1,21 @@
<UserControl x:Class="Daybreak.Controls.FireballGlyph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Grid>
<Line Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
X1="15" X2="26" Y1="11" Y2="0"></Line>
<Ellipse Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Width="4" Height="4"
Margin="8,13,13,8"></Ellipse>
<Path Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m15.025,0l-12.7,11.3c-3.1,3.1 -3.1,8.2 0,11.3s8.2,3.1 11.3,0l12.4,-11.6"></Path>
</Grid>
</Viewbox>
</UserControl>
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for FireballGlyph.xaml
/// </summary>
public partial class FireballGlyph : UserControl
{
public FireballGlyph()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,25 @@
<UserControl x:Class="Daybreak.Controls.AttributeTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="10, 0, 10, 0">
<local:HelpButton Width="20" Height="20" Margin="3, 0, 3, 0" Foreground="White"
Clicked="HelpButton_Clicked" Cursor="Hand"></local:HelpButton>
<TextBlock Text="{Binding Attribute.Name}" FontSize="16" Foreground="White"></TextBlock>
</WrapPanel>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="10, 0, 10, 0">
<local:MinusButton Foreground="White" Height="20" Clicked="MinusButton_Clicked" Cursor="Hand"
IsEnabled="{Binding ElementName=_this, Path=CanSubtract, Mode=OneWay}"></local:MinusButton>
<TextBox Background="Transparent" Foreground="White" FontSize="16" IsReadOnly="True" Width="30"
Text="{Binding Points}"></TextBox>
<local:AddButton Foreground="White" Height="20" Clicked="AddButton_Clicked" Cursor="Hand"
IsEnabled="{Binding ElementName=_this, Path=CanAdd, Mode=OneWay}"></local:AddButton>
</WrapPanel>
</Grid>
</UserControl>
@@ -0,0 +1,84 @@
using Daybreak.Models.Builds;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for AttributeTemplate.xaml
/// </summary>
public partial class AttributeTemplate : UserControl
{
public readonly static DependencyProperty CanAddProperty =
DependencyPropertyExtensions.Register<AttributeTemplate, bool>(nameof(CanAdd), new PropertyMetadata(false));
public readonly static DependencyProperty CanSubtractProperty =
DependencyPropertyExtensions.Register<AttributeTemplate, bool>(nameof(CanSubtract), new PropertyMetadata(false));
public event EventHandler<AttributeEntry> HelpClicked;
public bool CanAdd
{
get => this.GetTypedValue<bool>(CanAddProperty);
private set => this.SetValue(CanAddProperty, value);
}
public bool CanSubtract
{
get => this.GetTypedValue<bool>(CanSubtractProperty);
private set => this.SetValue(CanSubtractProperty, value);
}
public AttributeTemplate()
{
this.InitializeComponent();
this.DataContextChanged += AttributeTemplate_DataContextChanged;
}
private void AttributeTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is AttributeEntry attributeEntry)
{
if (attributeEntry.Points > 0)
{
this.CanSubtract = true;
}
if (attributeEntry.Points < 12)
{
this.CanAdd = true;
}
}
}
private void MinusButton_Clicked(object sender, System.EventArgs e)
{
if (this.DataContext.As<AttributeEntry>().Points > 0)
{
this.DataContext.As<AttributeEntry>().Points--;
this.CanSubtract = this.DataContext.As<AttributeEntry>().Points > 0;
this.CanAdd = true;
}
}
private void AddButton_Clicked(object sender, System.EventArgs e)
{
if (this.DataContext.As<AttributeEntry>().Points < 12)
{
this.DataContext.As<AttributeEntry>().Points++;
this.CanAdd = this.DataContext.As<AttributeEntry>().Points < 12;
this.CanSubtract = true;
}
}
private void HelpButton_Clicked(object sender, System.EventArgs e)
{
if (this.DataContext is AttributeEntry attributeEntry)
{
this.HelpClicked?.Invoke(this, attributeEntry);
}
}
}
}
@@ -0,0 +1,14 @@
<UserControl x:Class="Daybreak.Controls.BuildEntryTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
xmlns:controls="clr-namespace:Daybreak.Controls"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock FontSize="16" Foreground="White" Text="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
<controls:BinButton Width="30" Height="30" Foreground="White" HorizontalAlignment="Right" Clicked="BinButton_Clicked"></controls:BinButton>
</Grid>
</UserControl>
@@ -0,0 +1,27 @@
using Daybreak.Models.Builds;
using System;
using System.Windows.Controls;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for BuildEntryTemplate.xaml
/// </summary>
public partial class BuildEntryTemplate : UserControl
{
public event EventHandler<BuildEntry> RemoveClicked;
public BuildEntryTemplate()
{
InitializeComponent();
}
private void BinButton_Clicked(object sender, EventArgs e)
{
if (this.DataContext is BuildEntry buildEntry)
{
this.RemoveClicked?.Invoke(this, buildEntry);
}
}
}
}
@@ -0,0 +1,169 @@
<UserControl x:Class="Daybreak.Controls.BuildTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:Daybreak.Behaviors"
xmlns:converters="clr-namespace:Daybreak.Converters"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Transparent" MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Primary Profession: " FontSize="16" Foreground="White" Grid.Column="1"></TextBlock>
<ListView FontSize="16" Foreground="White" Grid.Column="2" BorderThickness="0"
Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Professions, Mode=OneWay}"
SelectedItem="{Binding ElementName=_this, Path=PrimaryProfession, Mode=TwoWay}" Height="30"
SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Disabled"
PreviewMouseWheel="ListView_NavigateWithMouseWheel">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, Mode=OneWay}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
<interactivity:Interaction.Behaviors>
<behaviors:ScrollIntoView></behaviors:ScrollIntoView>
</interactivity:Interaction.Behaviors>
</ListView>
<local:HelpButton Grid.Column="0" Foreground="White" Width="20" Height="20" Margin="3"
Clicked="HelpButtonPrimary_Clicked" Cursor="Hand"></local:HelpButton>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Secondary Profession: " FontSize="16" Foreground="White" Grid.Column="1"></TextBlock>
<ListView FontSize="16" Foreground="White" Grid.Column="2" BorderThickness="0"
Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Professions, Mode=OneWay}"
SelectedItem="{Binding ElementName=_this, Path=SecondaryProfession, Mode=TwoWay}" Height="30"
SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Disabled"
PreviewMouseWheel="ListView_NavigateWithMouseWheel">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, Mode=OneWay}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
<interactivity:Interaction.Behaviors>
<behaviors:ScrollIntoView></behaviors:ScrollIntoView>
</interactivity:Interaction.Behaviors>
</ListView>
<local:HelpButton Grid.Column="0" Foreground="White" Width="20" Height="20" Margin="3"
Clicked="HelpButtonSecondary_Clicked" Cursor="Hand"></local:HelpButton>
</Grid>
<Grid Grid.Row="3">
<ListBox Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Attributes, Mode=OneWay}"
HorizontalContentAlignment="Stretch" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<local:AttributeTemplate HelpClicked="AttributeTemplate_HelpClicked"></local:AttributeTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<local:SkillTemplate Grid.Column="0"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill0, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="1"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill1, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="2"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill2, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="3"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill3, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="4"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill4, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="5"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill5, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="6"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill6, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="7"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill7, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<TextBlock Grid.Column="0" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill0.Name, Mode=OneWay}"></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill1.Name, Mode=OneWay}"></TextBlock>
<TextBlock Grid.Column="2" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill2.Name, Mode=OneWay}"></TextBlock>
<TextBlock Grid.Column="3" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill3.Name, Mode=OneWay}"></TextBlock>
<TextBlock Grid.Column="4" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill4.Name, Mode=OneWay}"></TextBlock>
<TextBlock Grid.Column="5" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill5.Name, Mode=OneWay}"></TextBlock>
<TextBlock Grid.Column="6" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill6.Name, Mode=OneWay}"></TextBlock>
<TextBlock Grid.Column="7" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="White" Text="{Binding ElementName=_this, Path=Skill7.Name, Mode=OneWay}"></TextBlock>
</Grid>
<Grid Grid.Column="1" Grid.RowSpan="6">
<local:ChromiumBrowserWrapper x:Name="SkillBrowser" ControlsEnabled="False" Width="0" AddressBarReadonly="True" CanNavigate="True"></local:ChromiumBrowserWrapper>
</Grid>
<Grid Grid.Column="1" Grid.RowSpan="6">
<ListView x:Name="SkillsListView" Background="Transparent" Width="0"
ItemsSource="{Binding ElementName=_this, Path=AvailableSkills, Mode=OneWay}" MouseDoubleClick="ListView_MouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="16" Foreground="White" TextWrapping="Wrap" Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,367 @@
using Daybreak.Launch;
using Daybreak.Models.Builds;
using Daybreak.Services.IconRetrieve;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for BuildTemplate.xaml
/// </summary>
public partial class BuildTemplate : UserControl
{
private const string InfoNamePlaceholder = "[NAME]";
private const string BaseAddress = $"https://wiki.guildwars.com/wiki/{InfoNamePlaceholder}";
public readonly static DependencyProperty PrimaryProfessionProperty =
DependencyPropertyExtensions.Register<BuildTemplate, Profession>(nameof(PrimaryProfession), new PropertyMetadata(Profession.None));
public readonly static DependencyProperty SecondaryProfessionProperty =
DependencyPropertyExtensions.Register<BuildTemplate, Profession>(nameof(SecondaryProfession), new PropertyMetadata(Profession.None));
public readonly static DependencyProperty Skill0Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill0), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill1Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill1), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill2Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill2), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill3Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill3), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill4Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill4), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill5Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill5), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill6Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill6), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill7Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill7), new PropertyMetadata(Skill.NoSkill));
private BuildEntry loadedBuild;
private SkillTemplate selectingSkillTemplate;
public Profession PrimaryProfession
{
get => this.GetTypedValue<Profession>(PrimaryProfessionProperty);
set => this.SetValue(PrimaryProfessionProperty, value);
}
public Profession SecondaryProfession
{
get => this.GetTypedValue<Profession>(SecondaryProfessionProperty);
set => this.SetValue(SecondaryProfessionProperty, value);
}
public Skill Skill0
{
get => this.GetTypedValue<Skill>(Skill0Property);
set => this.SetValue(Skill0Property, value);
}
public Skill Skill1
{
get => this.GetTypedValue<Skill>(Skill1Property);
set => this.SetValue(Skill1Property, value);
}
public Skill Skill2
{
get => this.GetTypedValue<Skill>(Skill2Property);
set => this.SetValue(Skill2Property, value);
}
public Skill Skill3
{
get => this.GetTypedValue<Skill>(Skill3Property);
set => this.SetValue(Skill3Property, value);
}
public Skill Skill4
{
get => this.GetTypedValue<Skill>(Skill4Property);
set => this.SetValue(Skill4Property, value);
}
public Skill Skill5
{
get => this.GetTypedValue<Skill>(Skill5Property);
set => this.SetValue(Skill5Property, value);
}
public Skill Skill6
{
get => this.GetTypedValue<Skill>(Skill6Property);
set => this.SetValue(Skill6Property, value);
}
public Skill Skill7
{
get => this.GetTypedValue<Skill>(Skill7Property);
set => this.SetValue(Skill7Property, value);
}
public ObservableCollection<Skill> AvailableSkills { get; } = new ObservableCollection<Skill>();
public ObservableCollection<AttributeEntry> Attributes { get; } = new ObservableCollection<AttributeEntry>();
public ObservableCollection<Profession> Professions { get; } = new ObservableCollection<Profession>(Profession.Professions);
public BuildTemplate()
{
this.InitializeComponent();
this.DataContextChanged += BuildTemplate_DataContextChanged;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == PrimaryProfessionProperty || e.Property == SecondaryProfessionProperty)
{
if (e.Property == PrimaryProfessionProperty)
{
this.loadedBuild.Build.Primary = this.PrimaryProfession;
}
else
{
this.loadedBuild.Build.Secondary = this.SecondaryProfession;
}
this.LoadSkills();
this.LoadAttributes();
}
}
private void BuildTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue is BuildEntry)
{
this.LoadBuild();
this.LoadSkills();
this.LoadAttributes();
}
}
private void Grid_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.HideSkillListView();
this.HideInfoBrowser();
}
private void LoadAttributes()
{
var possibleAttributes = new List<AttributeEntry>();
if (this.PrimaryProfession != Profession.None)
{
possibleAttributes.Add(new AttributeEntry { Attribute = this.PrimaryProfession.PrimaryAttribute });
possibleAttributes.AddRange(this.PrimaryProfession.Attributes.Select(a => new AttributeEntry { Attribute = a }));
}
if (this.SecondaryProfession != Profession.None && this.SecondaryProfession != this.PrimaryProfession)
{
possibleAttributes.AddRange(this.SecondaryProfession.Attributes.Select(a => new AttributeEntry { Attribute = a }));
}
this.Attributes.ClearAnd().AddRange(possibleAttributes.Select(entry =>
{
var maybePresentAttribute = this.loadedBuild.Build.Attributes.Where(buildEntry => entry.Attribute == buildEntry.Attribute).FirstOrDefault();
if (maybePresentAttribute is null)
{
return entry;
}
entry.Points = maybePresentAttribute.Points;
return entry;
}));
this.loadedBuild.Build.Attributes = this.Attributes.ToList();
}
private void LoadSkills()
{
var possibleSkills = Skill.Skills
.Where(s => s.Profession == PrimaryProfession || s.Profession == SecondaryProfession || s.Profession == Profession.None)
.Where(s => s != Skill.NoSkill)
.OrderBy(s => s.Name);
this.AvailableSkills.ClearAnd().AddRange(possibleSkills);
if (this.Skill0.Profession != PrimaryProfession &&
this.Skill0.Profession != SecondaryProfession &&
this.Skill0.Profession != Profession.None)
{
this.Skill0 = Skill.NoSkill;
}
if (this.Skill1.Profession != PrimaryProfession &&
this.Skill1.Profession != SecondaryProfession &&
this.Skill1.Profession != Profession.None)
{
this.Skill1 = Skill.NoSkill;
}
if (this.Skill2.Profession != PrimaryProfession &&
this.Skill2.Profession != SecondaryProfession &&
this.Skill2.Profession != Profession.None)
{
this.Skill2 = Skill.NoSkill;
}
if (this.Skill3.Profession != PrimaryProfession &&
this.Skill3.Profession != SecondaryProfession &&
this.Skill3.Profession != Profession.None)
{
this.Skill3 = Skill.NoSkill;
}
if (this.Skill4.Profession != PrimaryProfession &&
this.Skill4.Profession != SecondaryProfession &&
this.Skill4.Profession != Profession.None)
{
this.Skill4 = Skill.NoSkill;
}
if (this.Skill5.Profession != PrimaryProfession &&
this.Skill5.Profession != SecondaryProfession &&
this.Skill5.Profession != Profession.None)
{
this.Skill5 = Skill.NoSkill;
}
if (this.Skill6.Profession != PrimaryProfession &&
this.Skill6.Profession != SecondaryProfession &&
this.Skill6.Profession != Profession.None)
{
this.Skill6 = Skill.NoSkill;
}
if (this.Skill7.Profession != PrimaryProfession &&
this.Skill7.Profession != SecondaryProfession &&
this.Skill7.Profession != Profession.None)
{
this.Skill7 = Skill.NoSkill;
}
}
private void LoadBuild()
{
var build = this.DataContext.As<BuildEntry>();
this.loadedBuild = build;
this.PrimaryProfession = build.Build.Primary;
this.SecondaryProfession = build.Build.Secondary;
this.Skill0 = build.Build.Skills[0];
this.Skill1 = build.Build.Skills[1];
this.Skill2 = build.Build.Skills[2];
this.Skill3 = build.Build.Skills[3];
this.Skill4 = build.Build.Skills[4];
this.Skill5 = build.Build.Skills[5];
this.Skill6 = build.Build.Skills[6];
this.Skill7 = build.Build.Skills[7];
}
private void BrowseToInfo(string infoName)
{
var address = BaseAddress.Replace(InfoNamePlaceholder, infoName.Replace(" ", "_"));
this.SkillBrowser.Address = address;
this.ShowInfoBrowser();
}
private void ShowInfoBrowser()
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
}
private void HideInfoBrowser()
{
this.SkillBrowser.Width = 0;
}
private void ShowSkillListView()
{
this.SkillBrowser.Width = 0;
this.SkillsListView.Width = 400;
}
private void HideSkillListView()
{
this.SkillsListView.Width = 0;
}
private void HelpButtonPrimary_Clicked(object sender, System.EventArgs e)
{
if (this.PrimaryProfession == Profession.None)
{
return;
}
this.BrowseToInfo(PrimaryProfession.Name);
if (e is RoutedEventArgs routedEventArgs)
{
routedEventArgs.Handled = true;
}
}
private void HelpButtonSecondary_Clicked(object sender, System.EventArgs e)
{
if (this.SecondaryProfession == Profession.None)
{
return;
}
this.BrowseToInfo(this.SecondaryProfession.Name);
if (e is RoutedEventArgs routedEventArgs)
{
routedEventArgs.Handled = true;
}
}
private void AttributeTemplate_HelpClicked(object sender, AttributeEntry e)
{
this.BrowseToInfo(e.Attribute.Name);
}
private void SkillTemplate_Clicked(object sender, RoutedEventArgs e)
{
var skill = sender.As<SkillTemplate>().DataContext.As<Skill>();
if (skill == Skill.NoSkill)
{
this.ShowSkillListView();
this.selectingSkillTemplate = sender.As<SkillTemplate>();
}
else
{
this.BrowseToInfo(skill.Name);
}
e.Handled = true;
}
private void SkillTemplate_RemoveClicked(object sender, System.EventArgs e)
{
sender.As<SkillTemplate>().DataContext = Skill.NoSkill;
}
private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (this.selectingSkillTemplate is null)
{
this.HideSkillListView();
return;
}
this.selectingSkillTemplate.DataContext = sender.As<ListView>().SelectedItem;
this.HideSkillListView();
this.loadedBuild.Build.Skills[0] = Skill0;
this.loadedBuild.Build.Skills[1] = Skill1;
this.loadedBuild.Build.Skills[2] = Skill2;
this.loadedBuild.Build.Skills[3] = Skill3;
this.loadedBuild.Build.Skills[4] = Skill4;
this.loadedBuild.Build.Skills[5] = Skill5;
this.loadedBuild.Build.Skills[6] = Skill6;
this.loadedBuild.Build.Skills[7] = Skill7;
}
private void ListView_NavigateWithMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
e.Handled = true;
if (e.Delta > 0)
{
sender.As<ListView>().SelectedIndex = sender.As<ListView>().SelectedIndex > 0 ?
sender.As<ListView>().SelectedIndex - 1 :
0;
}
else
{
sender.As<ListView>().SelectedIndex = sender.As<ListView>().SelectedIndex < sender.As<ListView>().Items.Count - 1 ?
sender.As<ListView>().SelectedIndex + 1 :
sender.As<ListView>().Items.Count;
}
}
}
}
@@ -0,0 +1,17 @@
<UserControl x:Class="Daybreak.Controls.Templates.ScreenTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls.Templates"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Rectangle Stroke="{Binding ElementName=_this, Path=Highlight, Mode=OneWay}" StrokeThickness="15"></Rectangle>
<TextBlock FontSize="168" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{Binding ElementName=_this, Path=ScreenId, Mode=OneWay}"
Foreground="{Binding ElementName=_this, Path=Highlight, Mode=OneWay}"></TextBlock>
<Rectangle Fill="Transparent" MouseEnter="Rectangle_MouseEnter" MouseLeave="Rectangle_MouseLeave" MouseLeftButtonDown="Rectangle_MouseLeftButtonDown"></Rectangle>
</Grid>
</UserControl>
@@ -0,0 +1,73 @@
using Daybreak.Models;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
namespace Daybreak.Controls.Templates
{
/// <summary>
/// Interaction logic for ScreenTemplate.xaml
/// </summary>
public partial class ScreenTemplate : UserControl
{
public static readonly DependencyProperty ScreenIdProperty =
DependencyPropertyExtensions.Register<ScreenTemplate, string>(nameof(ScreenId));
public static readonly DependencyProperty HighlightProperty =
DependencyPropertyExtensions.Register<ScreenTemplate, Brush>(nameof(Highlight));
public event EventHandler<Screen> Clicked;
public string ScreenId
{
get => this.GetTypedValue<string>(ScreenIdProperty);
set => this.SetValue(ScreenIdProperty, value);
}
public Brush Highlight
{
get => this.GetTypedValue<Brush>(HighlightProperty);
set => this.SetValue(HighlightProperty, value);
}
public ScreenTemplate()
{
this.InitializeComponent();
this.DataContextChanged += ScreenTemplate_DataContextChanged;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ForegroundProperty)
{
this.Highlight = e.NewValue.As<Brush>();
}
}
private void ScreenTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is Screen screen)
{
this.ScreenId = screen.Id.ToString();
}
}
private void Rectangle_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
this.Highlight = Brushes.LightSteelBlue;
}
private void Rectangle_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
this.Highlight = this.Foreground;
}
private void Rectangle_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.Clicked?.Invoke(this, this.DataContext.As<Screen>());
}
}
}
@@ -0,0 +1,34 @@
<UserControl x:Class="Daybreak.Controls.SkillTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</ResourceDictionary>
</UserControl.Resources>
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Image VerticalAlignment="Top" HorizontalAlignment="Left"
Width="{Binding ElementName=_this, Path=ActualWidth, Mode=OneWay}"
Height="{Binding ElementName=_this, Path=ActualWidth, Mode=OneWay}"
Source="{Binding ElementName=_this, Path=ImageSource, Mode=OneWay}"
Stretch="UniformToFill"></Image>
<Border BorderThickness="5" BorderBrush="Black"></Border>
<Border BorderThickness="5" BorderBrush="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Opacity="{Binding ElementName=_this, Path=BorderOpacity, Mode=OneWay}"></Border>
<Rectangle Fill="Transparent" MouseEnter="Border_MouseEnter" MouseLeave="Border_MouseLeave"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch" MouseLeftButtonDown="Rectangle_MouseLeftButtonDown"></Rectangle>
<local:CancelButton x:Name="CancelButton" Width="30" Height="30" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="10"
Visibility="Hidden" Foreground="White" Clicked="CancelButton_Clicked" MouseEnter="Border_MouseEnter"
MouseLeave="Border_MouseLeave"></local:CancelButton>
</Grid>
</UserControl>
@@ -0,0 +1,121 @@
using Daybreak.Launch;
using Daybreak.Models.Builds;
using Daybreak.Services.IconRetrieve;
using System;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for SkillTemplate.xaml
/// </summary>
public partial class SkillTemplate : UserControl
{
public readonly static DependencyProperty ImageSourceProperty =
DependencyPropertyExtensions.Register<SkillTemplate, ImageSource>(nameof(ImageSource));
public readonly static DependencyProperty BorderOpacityProperty =
DependencyPropertyExtensions.Register<SkillTemplate, double>(nameof(BorderOpacity), new PropertyMetadata(0d));
public event EventHandler<RoutedEventArgs> Clicked;
public event EventHandler RemoveClicked;
private readonly IIconRetriever iconRetriever;
public ImageSource ImageSource
{
get => this.GetTypedValue<ImageSource>(ImageSourceProperty);
set => this.SetValue(ImageSourceProperty, value);
}
public double BorderOpacity
{
get => this.GetTypedValue<double>(BorderOpacityProperty);
set => this.SetValue(BorderOpacityProperty, value);
}
public SkillTemplate()
{
this.iconRetriever = Launcher.ApplicationServiceManager.GetService<IIconRetriever>();
this.InitializeComponent();
this.DataContextChanged += SkillTemplate_DataContextChanged;
}
private void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is Skill skill)
{
if (skill != Skill.NoSkill)
{
Task.Run(() => GetImageStream(skill)).ContinueWith((previousTask) =>
{
this.Dispatcher.Invoke(() =>
{
this.ImageSource = GetImageSource(previousTask.Result);
});
});
}
else if (this.ImageSource is not null)
{
this.ImageSource = null;
}
}
}
private void Border_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
this.BorderOpacity = 1;
this.CancelButton.Visibility = this.HasSkill() ? Visibility.Visible : Visibility.Hidden;
}
private void Border_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
this.BorderOpacity = 0;
this.CancelButton.Visibility = Visibility.Hidden;
}
private void Rectangle_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.Clicked?.Invoke(this, e);
}
private void CancelButton_Clicked(object sender, EventArgs e)
{
this.RemoveClicked?.Invoke(this, e);
}
private bool HasSkill()
{
if (this.DataContext is Skill skill)
{
return skill != Skill.NoSkill;
}
return false;
}
private async Task<Stream> GetImageStream(Skill skill)
{
var maybeStream = await this.iconRetriever.GetIcon(skill);
return maybeStream.ExtractValue();
}
private ImageSource GetImageSource(Stream stream)
{
if (stream is null)
{
return null;
}
return this.Dispatcher.Invoke(() =>
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = stream;
bitmapImage.CacheOption = BitmapCacheOption.OnDemand;
bitmapImage.EndInit();
return bitmapImage;
});
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Daybreak.Converters
{
public class HiddenWhenNull : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GetVerticalAlignment(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private object GetVerticalAlignment(object value)
{
if (value is null)
{
return Visibility.Hidden;
}
else
{
return Visibility.Visible;
}
}
}
}
+27 -11
View File
@@ -9,39 +9,55 @@
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<Version>0.6.6</Version>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.8.6</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.774.44" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.818.41" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Slim" Version="1.2.1" />
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
<PackageReference Include="Slim" Version="1.2.2" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.1.3" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.1.4" />
<PackageReference Include="WCL" Version="1.0.2" />
<PackageReference Include="WpfExtended" Version="0.1.1" />
<PackageReference Include="WpfExtended" Version="0.2.1" />
<PackageReference Include="WpfScreenHelper" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Controls\StarGlyph.xaml.cs">
<Compile Update="Controls\Buttons\HelpButton.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Controls\CancelButton.xaml.cs">
<Compile Update="Controls\Buttons\MinusButton.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Controls\Glyphs\StarGlyph.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Controls\Buttons\CancelButton.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\MaximizeButton.xaml">
<Page Update="Controls\Buttons\HelpButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Controls\StarGlyph.xaml">
<Page Update="Controls\Buttons\MinusButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Controls\CancelButton.xaml">
<Page Update="Controls\Buttons\MaximizeButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Controls\Glyphs\StarGlyph.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Controls\Buttons\CancelButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
+14
View File
@@ -9,6 +9,8 @@ using Microsoft.Web.WebView2.Wpf;
using Slim;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Extensions;
@@ -45,6 +47,18 @@ namespace Daybreak.Launch
MessageBox.Show(fatalException.ToString());
return false;
}
else if (e is AggregateException aggregateException)
{
if (aggregateException.InnerExceptions.FirstOrDefault() is COMException comException &&
comException.Message.Contains("Invalid window handle"))
{
/*
* Ignore exception caused by browser failing to initialize due to missing window.
* Likely caused by switching windows before browser was initialized.
*/
return true;
}
}
MessageBox.Show(e.ToString());
return true;
+54 -43
View File
@@ -16,43 +16,46 @@
Title="Daybreak"
d:DesignHeight="450" d:DesignWidth="800">
<Window.Resources>
<Style x:Key="Window_SettingsButton" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Rectangle x:Name="OverlayRect" Fill="{TemplateBinding Background}" Opacity="0"></Rectangle>
<Viewbox Margin="5" Stretch="Uniform">
<Grid>
<Path Stroke="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
<Style x:Key="Window_SettingsButton" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Rectangle x:Name="OverlayRect" Fill="{TemplateBinding Background}" Opacity="0"></Rectangle>
<Viewbox Margin="5" Stretch="Uniform">
<Grid>
<Path Stroke="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
Data="m76.89,45.018c0,-1.587 -0.151,-3.138 -0.383,-4.66l11.227,-6.48l-11.639,-20.136l-11.616,6.47c-2.421,-1.931 -5.479,-3.506 -8.479,-4.664l0,-11.548l-22,0l0,11.548c-4,1.112 -5.649,2.617 -8.005,4.448l-11.251,-6.604l-11.914,20.006l11.12,6.604c-0.262,1.643 -0.45,3.308 -0.45,5.016c0,1.587 0.153,3.136 0.376,4.658l-11.228,6.484l11.634,20.135l11.424,-6.472c2.431,1.933 4.294,3.506 8.294,4.665l0,12.512l22,0l0,-12.512c3,-1.115 5.839,-2.618 8.19,-4.449l11.348,6.604l11.96,-20.006l-11.095,-6.603c0.258,-1.64 0.487,-3.304 0.487,-5.016zm-31.704,14.137c-7.799,0 -14.138,-6.332 -14.138,-14.138c0,-7.808 6.339,-14.136 14.138,-14.136c7.81,0 14.14,6.328 14.14,14.136c-0.001,7.806 -6.331,14.138 -14.14,14.138z"></Path>
<Path Fill="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
<Path Fill="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
Data="m56.5,89l-22,0c-1.381,0 -2.5,-1.119 -2.5,-2.5l0,-10.199c-2.612,-0.955 -4.464,-2.126 -6.274,-3.49l-10.229,5.671c-1.192,0.661 -2.694,0.244 -3.377,-0.936l-11.633,-20.135c-0.331,-0.574 -0.422,-1.257 -0.25,-1.897s0.591,-1.188 1.165,-1.519l9.794,-5.654c-0.121,-1.164 -0.181,-2.262 -0.181,-3.323c0,-1.317 0.112,-2.592 0.254,-3.721l-9.656,-5.75c-0.57,-0.34 -0.982,-0.893 -1.145,-1.537c-0.163,-0.644 -0.063,-1.326 0.279,-1.896l11.977,-20.006c0.702,-1.173 2.218,-1.564 3.399,-0.878l9.927,5.762c1.618,-1.213 3.362,-2.346 5.949,-3.273l0,-10.219c0.001,-1.381 1.12,-2.5 2.501,-2.5l22,0c1.381,0 2.5,1.119 2.5,2.5l0,10.377c2.124,0.938 4.104,2.059 5.919,3.35l9.939,-5.657c1.19,-0.677 2.713,-0.269 3.4,0.922l11.639,20.135c0.332,0.574 0.423,1.257 0.251,1.897s-0.591,1.188 -1.165,1.519l-9.781,5.646c0.126,1.165 0.188,2.266 0.188,3.329c0,1.327 -0.108,2.597 -0.246,3.717l9.692,5.752c0.571,0.339 0.983,0.891 1.147,1.533c0.163,0.644 0.065,1.325 -0.274,1.896l-11.897,20.005c-0.703,1.183 -2.234,1.573 -3.416,0.876l-9.798,-5.765c-1.782,1.25 -3.634,2.29 -5.598,3.144l0,10.324c0,1.381 -1.119,2.5 -2.5,2.5zm-19.5,-5l17,0l0,-9.512c0,-1.045 0.649,-1.979 1.629,-2.344c2.624,-0.975 5.004,-2.309 7.275,-4.078c0.809,-0.629 1.921,-0.702 2.804,-0.182l9.078,5.341l9.347,-15.716l-8.974,-5.325c-0.88,-0.522 -1.354,-1.528 -1.194,-2.538c0.198,-1.259 0.425,-2.923 0.425,-4.629c0,-1.314 -0.116,-2.716 -0.354,-4.284c-0.154,-1.018 0.33,-2.026 1.222,-2.541l9.061,-5.23l-9.145,-15.818l-9.208,5.241c-0.888,0.505 -1.995,0.42 -2.795,-0.219c-2.199,-1.754 -4.747,-3.196 -7.57,-4.286c-0.964,-0.372 -1.6,-1.299 -1.6,-2.332l0,-9.548l-17.001,0l0,9.548c0,1.123 -0.748,2.107 -1.83,2.408c-3.47,0.965 -5.073,2.212 -7.293,3.938l-0.098,0.076c-0.804,0.625 -1.909,0.7 -2.789,0.188l-9.243,-5.365l-9.423,15.739l8.937,5.321c0.879,0.524 1.351,1.531 1.189,2.542c-0.202,1.27 -0.435,2.943 -0.435,4.622c0,1.318 0.112,2.724 0.342,4.297c0.148,1.013 -0.337,2.015 -1.224,2.526l-9.065,5.234l9.151,15.842l9.525,-5.28c0.883,-0.49 1.976,-0.4 2.768,0.229c2.384,1.896 4.207,3.214 7.684,4.221c1.069,0.31 1.804,1.29 1.804,2.402l0,9.512zm8.186,-22.345c-9.174,0 -16.638,-7.464 -16.638,-16.638c0,-9.173 7.464,-16.636 16.638,-16.636c9.175,0 16.64,7.463 16.64,16.636c-0.001,9.174 -7.466,16.638 -16.64,16.638zm0,-28.273c-6.417,0 -11.638,5.22 -11.638,11.636c0,6.417 5.221,11.638 11.638,11.638c6.418,0 11.64,-5.221 11.64,-11.638c-0.001,-6.416 -5.222,-11.636 -11.64,-11.636z"></Path>
</Grid>
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0.5"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid>
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0.5"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid Background="Black">
<Grid.RowDefinitions>
@@ -69,8 +72,6 @@
</controls:ImageViewer.Effect>
</controls:ImageViewer>
</Border>
<Grid x:Name="Container" Grid.Row="1">
</Grid>
<wcl:TitleBar x:Name="Titlebar"
Background="Transparent" MouseLeftButtonDown="TitleBar_MouseLeftButtonDown"
WindowState="Normal" MinimizeButtonClicked="TitleBar_MinimizeButtonClicked"
@@ -80,13 +81,23 @@
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></wcl:TitleBar>
<ToggleButton Style="{StaticResource Window_SettingsButton}" Width="50" Height="30" HorizontalAlignment="Right"
Margin="0, 0, 150, 0" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Click="SettingsButton_Clicked"></ToggleButton>
<TextBox Grid.Row="1" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Opacity="0.6"
Text="{Binding ElementName=_this, Path=CreditText, Mode=OneWay}" IsReadOnly="True" VerticalAlignment="Bottom"
<controls:OpaqueButton Grid.Row="1" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Opacity="0.6"
Text="{Binding ElementName=_this, Path=CreditText, Mode=OneWay}" VerticalAlignment="Bottom"
HorizontalAlignment="Right" Margin="0, 0, 20, 30" FontSize="22" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
PreviewMouseLeftButtonDown="CreditTextBox_MouseLeftButtonDown" Cursor="Hand"></TextBox>
<TextBox Grid.Row="1" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Opacity="0.6"
Text="{Binding ElementName=_this, Path=CurrentVersionText, Mode=OneWay}" IsReadOnly="True" VerticalAlignment="Bottom"
HorizontalAlignment="Right" Margin="0, 0, 20, 10" FontSize="10" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></TextBox>
Clicked="CreditTextBox_MouseLeftButtonDown" Cursor="Hand"></controls:OpaqueButton>
<WrapPanel Grid.Row="1" Margin="0, 0, 20, 10" VerticalAlignment="Bottom" HorizontalAlignment="Right">
<TextBox Grid.Row="1" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Opacity="0.6"
Text="{Binding ElementName=_this, Path=CurrentVersionText, Mode=OneWay}" IsReadOnly="True" FontSize="10"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></TextBox>
<TextBox Grid.Row="1" Background="Transparent" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Opacity="0.6" Text=" - " FontSize="10" BorderBrush="Transparent" BorderThickness="0"
Visibility="{Binding ElementName=_this, Path=IsRunningAsAdmin, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"></TextBox>
<TextBox Grid.Row="1" Background="Transparent" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Opacity="0.6" Text="[Admin]" FontSize="10" BorderBrush="Transparent" BorderThickness="0"
Visibility="{Binding ElementName=_this, Path=IsRunningAsAdmin, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"></TextBox>
</WrapPanel>
<Grid x:Name="Container" Grid.Row="1">
</Grid>
<wcl:Border OnResize="Border_OnResize" Grid.RowSpan="2" Active="True"></wcl:Border>
</Grid>
</Window>
+14 -2
View File
@@ -1,4 +1,5 @@
using Daybreak.Services.Bloogum;
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
@@ -25,11 +26,13 @@ namespace Daybreak.Launch
{
public static readonly DependencyProperty CreditTextProperty = DependencyPropertyExtensions.Register<MainWindow, string>(nameof(CreditText));
public static readonly DependencyProperty CurrentVersionTextProperty = DependencyPropertyExtensions.Register<MainWindow, string>(nameof(CurrentVersionText));
public static readonly DependencyProperty IsRunningAsAdminProperty = DependencyPropertyExtensions.Register<MainWindow, bool>(nameof(IsRunningAsAdmin));
private readonly IViewManager viewManager;
private readonly IScreenshotProvider screenshotProvider;
private readonly IBloogumClient bloogumClient;
private readonly IApplicationUpdater applicationUpdater;
private readonly IPrivilegeManager privilegeManager;
private readonly CancellationTokenSource cancellationToken = new();
public string CreditText
@@ -44,18 +47,27 @@ namespace Daybreak.Launch
set => this.SetValue(CurrentVersionTextProperty, value);
}
public bool IsRunningAsAdmin
{
get => this.GetTypedValue<bool>(IsRunningAsAdminProperty);
set => this.SetValue(IsRunningAsAdminProperty, value);
}
public MainWindow(
IViewManager viewManager,
IScreenshotProvider screenshotProvider,
IBloogumClient bloogumClient,
IApplicationUpdater applicationUpdater)
IApplicationUpdater applicationUpdater,
IPrivilegeManager privilegeManager)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenshotProvider = screenshotProvider.ThrowIfNull(nameof(screenshotProvider));
this.bloogumClient = bloogumClient.ThrowIfNull(nameof(bloogumClient));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
this.InitializeComponent();
this.CurrentVersionText = this.applicationUpdater.CurrentVersion;
this.IsRunningAsAdmin = this.privilegeManager.AdminPrivileges;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
@@ -132,7 +144,7 @@ namespace Daybreak.Launch
this.viewManager.ShowView<SettingsCategoryView>();
}
private void CreditTextBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
private void CreditTextBox_MouseLeftButtonDown(object sender, EventArgs e)
{
if (Uri.TryCreate(this.CreditText, UriKind.Absolute, out var uri))
{
+23
View File
@@ -0,0 +1,23 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Daybreak.Models.Browser
{
public class BrowserPayload
{
[JsonConverter(typeof(StringEnumConverter))]
public enum PayloadKeys
{
ContextMenu
}
[JsonProperty("Key")]
public PayloadKeys Key { get; set; }
}
public sealed class BrowserPayload<T> : BrowserPayload
{
[JsonProperty("Value")]
public T Value { get; set; }
}
}
@@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace Daybreak.Models.Browser
{
public sealed class OnContextMenuPayload
{
[JsonProperty("X")]
public double X { get; set; }
[JsonProperty("Y")]
public double Y { get; set; }
[JsonProperty("Selection")]
public string Selection { get; set; }
}
}
+143
View File
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Daybreak.Models.Builds
{
public sealed class Attribute
{
public static Attribute FastCasting { get; } = new() { Name = "Fast Casting", Id = 0 };
public static Attribute IllusionMagic { get; } = new() { Name = "Illusion Magic", Id = 1 };
public static Attribute DominationMagic { get; } = new() { Name = "Domination Magic", Id = 2 };
public static Attribute InspirationMagic { get; } = new() { Name = "Inspiration Magic", Id = 3 };
public static Attribute BloodMagic { get; } = new() { Name = "Blood Magic", Id = 4 };
public static Attribute DeathMagic { get; } = new() { Name = "Death Magic", Id = 5 };
public static Attribute SoulReaping { get; } = new() { Name = "Soul Reaping", Id = 6 };
public static Attribute Curses { get; } = new() { Name = "Curses", Id = 7 };
public static Attribute AirMagic { get; } = new() { Name = "Air Magic", Id = 8 };
public static Attribute EarthMagic { get; } = new() { Name = "Earth Magic", Id = 9 };
public static Attribute FireMagic { get; } = new() { Name = "Fire Magic", Id = 10 };
public static Attribute WaterMagic { get; } = new() { Name = "Water Magic", Id = 11 };
public static Attribute EnergyStorage { get; } = new() { Name = "Energy Storage", Id = 12 };
public static Attribute HealingPrayers { get; } = new() { Name = "Healing Prayers", Id = 13 };
public static Attribute SmitingPrayers { get; } = new() { Name = "Smiting Prayers", Id = 14 };
public static Attribute ProtectionPrayers { get; } = new() { Name = "Protection Prayers", Id = 15 };
public static Attribute DivineFavor { get; } = new() { Name = "Divine Favor", Id = 16 };
public static Attribute Strength { get; } = new() { Name = "Strength", Id = 17 };
public static Attribute AxeMastery { get; } = new() { Name = "Axe Mastery", Id = 18 };
public static Attribute HammerMastery { get; } = new() { Name = "Hammer Mastery", Id = 19 };
public static Attribute Swordsmanship { get; } = new() { Name = "Swordsmanship", Id = 20};
public static Attribute Tactics { get; } = new() { Name = "Tactics", Id = 21 };
public static Attribute BeastMastery { get; } = new() { Name = "Beast Mastery", Id = 22 };
public static Attribute Expertise { get; } = new() { Name = "Expertise", Id = 23 };
public static Attribute WildernessSurvival { get; } = new() { Name = "Wilderness Survival", Id = 24 };
public static Attribute Marksmanship { get; } = new() { Name = "Marksmanship", Id = 25 };
public static Attribute DaggerMastery { get; } = new() { Name = "Dagger Mastery", Id = 29 };
public static Attribute DeadlyArts { get; } = new() { Name = "Deadly Arts", Id = 30 };
public static Attribute ShadowArts { get; } = new() { Name = "Shadow Arts", Id = 31 };
public static Attribute Communing { get; } = new() { Name = "Communing", Id = 32 };
public static Attribute RestorationMagic { get; } = new() { Name = "Restoration Magic", Id = 33 };
public static Attribute ChannelingMagic { get; } = new() { Name = "Channeling Magic", Id = 34 };
public static Attribute CriticalStrikes { get; } = new() { Name = "Critical Strikes", Id = 35 };
public static Attribute SpawningPower { get; } = new() { Name = "Spawning Power", Id = 36 };
public static Attribute SpearMastery { get; } = new() { Name = "Spear Mastery", Id = 37 };
public static Attribute Command { get; } = new() { Name = "Command", Id = 38 };
public static Attribute Motivation { get; } = new() { Name = "Motivation", Id = 39 };
public static Attribute Leadership { get; } = new() { Name = "Leadership", Id = 40 };
public static Attribute ScytheMastery { get; } = new() { Name = "Scythe Mastery", Id = 41 };
public static Attribute WindPrayers { get; } = new() { Name = "Wind Prayers", Id = 42 };
public static Attribute EarthPrayers { get; } = new() { Name = "Earth Prayers", Id = 43 };
public static Attribute Mysticism { get; } = new() { Name = "Mysticism", Id = 44 };
public static IEnumerable<Attribute> Attributes { get; } = new List<Attribute>
{
FastCasting,
IllusionMagic,
DominationMagic,
InspirationMagic,
BloodMagic,
DeathMagic,
SoulReaping,
Curses,
AirMagic,
EarthMagic,
FireMagic,
WaterMagic,
EnergyStorage,
HealingPrayers,
SmitingPrayers,
ProtectionPrayers,
DivineFavor,
Strength,
AxeMastery,
HammerMastery,
Swordsmanship,
Tactics,
BeastMastery,
Expertise,
WildernessSurvival,
Marksmanship,
DaggerMastery,
DeadlyArts,
ShadowArts,
Communing,
RestorationMagic,
ChannelingMagic,
CriticalStrikes,
SpawningPower,
SpearMastery,
Command,
Motivation,
Leadership,
ScytheMastery,
EarthPrayers,
WindPrayers,
EarthMagic,
Mysticism
};
public static bool TryParse(int id, out Attribute attribute)
{
attribute = Attributes.Where(attr => attr.Id == id).FirstOrDefault();
if (attribute is null)
{
return false;
}
return true;
}
public static bool TryParse(string name, out Attribute attribute)
{
attribute = Attributes.Where(attr => attr.Name == name).FirstOrDefault();
if (attribute is null)
{
return false;
}
return true;
}
public static Attribute Parse(int id)
{
if (TryParse(id, out var attribute) is false)
{
throw new InvalidOperationException($"Could not find an attribute with id {id}");
}
return attribute;
}
public static Attribute Parse(string name)
{
if (TryParse(name, out var attribute) is false)
{
throw new InvalidOperationException($"Could not find an attribute with name {name}");
}
return attribute;
}
public int Id { get; private set; }
public string Name { get; private set; }
private Attribute()
{
}
}
}
+31
View File
@@ -0,0 +1,31 @@
using System.ComponentModel;
namespace Daybreak.Models.Builds
{
public sealed class AttributeEntry : INotifyPropertyChanged
{
private Attribute attribute;
private int points;
public Attribute Attribute
{
get => this.attribute;
set
{
this.attribute = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Attribute)));
}
}
public int Points
{
get => this.points;
set
{
this.points = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Points)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
+13
View File
@@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace Daybreak.Models.Builds
{
public sealed class Build
{
public BuildMetadata BuildMetadata { get; set; }
public Profession Primary { get; set; } = Profession.None;
public Profession Secondary { get; set; } = Profession.None;
public List<AttributeEntry> Attributes { get; set; } = new();
public List<Skill> Skills { get; set; } = new() { Skill.NoSkill, Skill.NoSkill, Skill.NoSkill, Skill.NoSkill, Skill.NoSkill, Skill.NoSkill, Skill.NoSkill, Skill.NoSkill };
}
}
+31
View File
@@ -0,0 +1,31 @@
using System.ComponentModel;
namespace Daybreak.Models.Builds
{
public sealed class BuildEntry : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
private Build build;
public string PreviousName { get; set; }
public string Name
{
get => this.name;
set
{
this.name = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public Build Build
{
get => this.build;
set
{
this.build = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Build)));
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
using System.Collections.Generic;
namespace Daybreak.Models.Builds
{
public sealed class BuildMetadata
{
public List<int> Base64Decoded { get; set; }
public List<string> BinaryDecoded { get; set; }
public int Header { get; set; }
public int VersionNumber { get; set; }
public int ProfessionIdLength { get; set; }
public int PrimaryProfessionId { get; set; }
public int SecondaryProfessionId { get; set; }
public int AttributeCount { get; set; }
public int AttributesLength { get; set; }
public int SkillsLength { get; set; }
public bool TailPresent { get; set; }
public bool NewTemplate { get; set; }
public List<int> SkillIds { get; set; } = new();
public List<int> AttributesIds { get; set; } = new();
public List<int> AttributePoints { get; set; } = new();
}
}
+81
View File
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Daybreak.Models.Builds
{
public sealed class Profession
{
public static Profession None { get; } = new() { Name = "None", Id = 0, };
public static Profession Warrior { get; } = new() { Name = "Warrior", Id = 1, PrimaryAttribute = Attribute.Strength, Attributes = new List<Attribute> { Attribute.AxeMastery, Attribute.HammerMastery, Attribute.Swordsmanship, Attribute.Tactics } };
public static Profession Ranger { get; } = new() { Name = "Ranger", Id = 2, PrimaryAttribute = Attribute.Expertise, Attributes = new List<Attribute> { Attribute.BeastMastery, Attribute.Marksmanship, Attribute.WildernessSurvival } };
public static Profession Monk { get; } = new() { Name = "Monk", Id = 3, PrimaryAttribute = Attribute.DivineFavor, Attributes = new List<Attribute> { Attribute.HealingPrayers, Attribute.SmitingPrayers, Attribute.ProtectionPrayers } };
public static Profession Necromancer { get; } = new() { Name = "Necromancer", Id = 4, PrimaryAttribute = Attribute.SoulReaping, Attributes = new List<Attribute> { Attribute.Curses, Attribute.BloodMagic, Attribute.DeathMagic } };
public static Profession Mesmer { get; } = new() { Name = "Mesmer", Id = 5, PrimaryAttribute = Attribute.FastCasting, Attributes = new List<Attribute> { Attribute.DominationMagic, Attribute.IllusionMagic, Attribute.InspirationMagic } };
public static Profession Elementalist { get; } = new() { Name = "Elementalist", Id = 6, PrimaryAttribute = Attribute.EnergyStorage, Attributes = new List<Attribute> { Attribute.AirMagic, Attribute.EarthMagic, Attribute.FireMagic, Attribute.WaterMagic } };
public static Profession Assassin { get; } = new() { Name = "Assassin", Id = 7, PrimaryAttribute = Attribute.CriticalStrikes, Attributes = new List<Attribute> { Attribute.DaggerMastery, Attribute.DeadlyArts, Attribute.ShadowArts } };
public static Profession Ritualist { get; } = new() { Name = "Ritualist", Id = 8, PrimaryAttribute = Attribute.SpawningPower, Attributes = new List<Attribute> { Attribute.ChannelingMagic, Attribute.Communing, Attribute.RestorationMagic } };
public static Profession Paragon { get; } = new() { Name = "Paragon", Id = 9, PrimaryAttribute = Attribute.Leadership, Attributes = new List<Attribute> { Attribute.Command, Attribute.Motivation, Attribute.SpearMastery } };
public static Profession Dervish { get; } = new() { Name = "Dervish", Id = 10, PrimaryAttribute = Attribute.Mysticism, Attributes = new List<Attribute> { Attribute.EarthPrayers, Attribute.ScytheMastery, Attribute.WindPrayers } };
public static IEnumerable<Profession> Professions { get; } = new List<Profession>
{
None,
Warrior,
Ranger,
Monk,
Necromancer,
Mesmer,
Elementalist,
Assassin,
Ritualist,
Paragon,
Dervish
};
public static bool TryParse(int id, out Profession profession)
{
profession = Professions.Where(prof => prof.Id == id).FirstOrDefault();
if (profession is null)
{
return false;
}
return true;
}
public static bool TryParse(string name, out Profession profession)
{
profession = Professions.Where(prof => prof.Name == name).FirstOrDefault();
if (profession is null)
{
return false;
}
return true;
}
public static Profession Parse(int id)
{
if (TryParse(id, out var profession) is false)
{
throw new InvalidOperationException($"Could not find a profession with id {id}");
}
return profession;
}
public static Profession Parse(string name)
{
if (TryParse(name, out var profession) is false)
{
throw new InvalidOperationException($"Could not find a profession with name {name}");
}
return profession;
}
public string Name { get; private set; }
public int Id { get; set; }
public Attribute PrimaryAttribute { get; private set; }
public List<Attribute> Attributes { get; private set; }
private Profession()
{
}
}
}
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace Daybreak.Models
{
public sealed class ElevationRequest
{
public object DataContext { get; set; }
public Type View { get; set; }
public string MessageToUser { get; set; }
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Windows.Forms;
namespace Daybreak.Models
{
public sealed class KeyMacro
{
public List<Keys> Keys { get; set; }
public Keys TargetKey { get; set; }
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace Daybreak.Models
{
public sealed class KeyboardHookEventArgs
{
public bool Handled { get; set; }
public KeyboardState KeyboardState { get; }
public KeyboardInput KeyboardInput { get; }
public KeyboardHookEventArgs(
KeyboardState keyboardState,
KeyboardInput keyboardInput)
{
this.KeyboardState = keyboardState;
this.KeyboardInput = keyboardInput;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Daybreak.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct KeyboardInput
{
/// <summary>
/// A virtual-key code. The code must be a value in the range 1 to 254.
/// </summary>
public int VirtualCode;
// EDT: added a conversion from VirtualCode to Keys.
/// <summary>
/// The VirtualCode converted to typeof(Keys) for higher usability.
/// </summary>
public Keys Key { get { return (Keys)VirtualCode; } }
/// <summary>
/// A hardware scan code for the key.
/// </summary>
public int HardwareScanCode;
/// <summary>
/// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level.
/// </summary>
public int Flags;
/// <summary>
/// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message.
/// </summary>
public int TimeStamp;
/// <summary>
/// Additional information associated with the message.
/// </summary>
public IntPtr AdditionalInformation;
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Daybreak.Models
{
public enum KeyboardState
{
KeyDown = 0x0100,
KeyUp = 0x0101,
SysKeyDown = 0x0104,
SysKeyUp = 0x0105
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.Windows;
namespace Daybreak.Models
{
public sealed class Screen
{
public int Id { get; set; }
public Rect Size { get; set; }
}
}
@@ -3,7 +3,9 @@ using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Privilege;
using Daybreak.Utils;
using Daybreak.Views;
using Microsoft.Win32;
using Pepa.Wpf.Utilities;
using System;
@@ -15,11 +17,13 @@ using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Daybreak.Services.ApplicationLauncher
{
public class ApplicationLauncher : IApplicationLauncher
{
private const int MaxRetries = 10;
private const string TexModProcessName = "TexMod";
private const string UModProcessName = "uMod";
private const string ToolboxProcessName = "GWToolbox";
@@ -30,6 +34,7 @@ namespace Daybreak.Services.ApplicationLauncher
private readonly ICredentialManager credentialManager;
private readonly IMutexHandler mutexHandler;
private readonly ILogger logger;
private readonly IPrivilegeManager privilegeManager;
public bool IsTexmodRunning => TexModProcessDetected();
public bool IsGuildwarsRunning => this.GuildwarsProcessDetected();
@@ -39,32 +44,41 @@ namespace Daybreak.Services.ApplicationLauncher
IConfigurationManager configurationManager,
ICredentialManager credentialManager,
IMutexHandler mutexHandler,
ILogger logger)
ILogger logger,
IPrivilegeManager privilegeManager)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.mutexHandler = mutexHandler.ThrowIfNull(nameof(mutexHandler));
this.credentialManager = credentialManager.ThrowIfNull(nameof(credentialManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
}
public async Task LaunchGuildwars()
public async Task<bool> LaunchGuildwars()
{
var configuration = this.configurationManager.GetConfiguration();
var auth = await this.credentialManager.GetDefaultCredentials().ConfigureAwait(false);
auth.Do(
onSome: (credentials) =>
return await auth.Switch(
onSome: async (credentials) =>
{
if (configuration.ExperimentalFeatures.MultiLaunchSupport is true)
{
if (this.privilegeManager.AdminPrivileges is false)
{
this.privilegeManager.RequestAdminPrivileges<MainView>("You need administrator rights in order to start using multi-launch");
return false;
}
ClearGwLocks();
}
LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
return await LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
},
onNone: () =>
{
throw new CredentialsNotFoundException($"No credentials available");
});
})
.ExtractValue();
}
public Task LaunchGuildwarsToolbox()
@@ -103,7 +117,34 @@ namespace Daybreak.Services.ApplicationLauncher
});
}
private void LaunchGuildwarsProcess(string email, Models.SecureString password, string character)
public void RestartDaybreakAsAdmin()
{
this.logger.LogInformation("Restarting daybreak with admin rights");
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
if (processName.IsNullOrWhiteSpace() || File.Exists(processName) is false)
{
throw new InvalidOperationException("Unable to find executable. Aborting restart");
}
var process = new Process()
{
StartInfo = new()
{
Verb = "runas",
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = true,
FileName = processName
}
};
if (process.Start() is false)
{
throw new InvalidOperationException($"Unable to start {processName} as admin");
}
Application.Current.Shutdown();
}
private async Task<bool> LaunchGuildwarsProcess(string email, Models.SecureString password, string character)
{
var executable = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (executable is null)
@@ -129,10 +170,54 @@ namespace Daybreak.Services.ApplicationLauncher
args.Add(character);
}
if (Process.Start(executable.Path, args) is null)
var identity = this.configurationManager.GetConfiguration().ExperimentalFeatures.LaunchGuildwarsAsCurrentUser ?
System.Security.Principal.WindowsIdentity.GetCurrent().Name :
System.Security.Principal.WindowsIdentity.GetAnonymous().Name;
this.logger.LogInformation($"Launching guildwars as [{identity}] identity");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
Arguments = string.Join(" ", args),
FileName = executable.Path
}
};
if (process.Start() is false)
{
throw new InvalidOperationException($"Unable to launch {executable}");
}
var retries = 0;
while (true)
{
await Task.Delay(100);
retries++;
var gwProcess = Process.GetProcessesByName("gw").FirstOrDefault();
if (gwProcess is null && retries < MaxRetries)
{
continue;
}
else if (gwProcess is null && retries >= MaxRetries)
{
throw new InvalidOperationException("Newly launched gw process not detected");
}
if (gwProcess.MainWindowHandle == IntPtr.Zero)
{
continue;
}
int titleLength = NativeMethods.GetWindowTextLength(gwProcess.MainWindowHandle);
var titleBuffer = new StringBuilder(titleLength);
var readCount = NativeMethods.GetWindowText(gwProcess.MainWindowHandle, titleBuffer, titleLength + 1);
var title = titleBuffer.ToString();
if (title != "Guild Wars")
{
continue;
}
return true;
}
}
private bool GuildwarsProcessDetected()
@@ -7,8 +7,9 @@ namespace Daybreak.Services.ApplicationLauncher
bool IsGuildwarsRunning { get; }
bool IsToolboxRunning { get; }
bool IsTexmodRunning { get; }
Task LaunchGuildwars();
Task<bool> LaunchGuildwars();
Task LaunchGuildwarsToolbox();
Task LaunchTexmod();
void RestartDaybreakAsAdmin();
}
}
@@ -0,0 +1,350 @@
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates.Models;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using System;
using System.Collections.Generic;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Text;
namespace Daybreak.Services.BuildTemplates
{
public sealed class BuildTemplateManager : IBuildTemplateManager
{
private const string DecodingLookupTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
private readonly static string BuildsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Guild Wars\\Templates\\Skills";
private readonly ILogger logger;
public BuildTemplateManager(
ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
}
public bool IsTemplate(string template)
{
if (template.IsNullOrWhiteSpace())
{
return false;
}
if (template.Where(c => DecodingLookupTable.Contains(c) is false).Any())
{
return false;
}
return true;
}
public BuildEntry CreateBuild()
{
var emptyBuild = new Build();
var builds = GetBuilds();
var baseName = "New build";
var name = baseName;
int count = 0;
while(builds.Where(b => b.Name.Equals(name, StringComparison.OrdinalIgnoreCase)).Any())
{
name = baseName + count;
count++;
}
var entry = new BuildEntry { Build = emptyBuild, Name = name, PreviousName = string.Empty };
this.SaveBuild(entry);
return entry;
}
public BuildEntry CreateBuild(string name)
{
var emptyBuild = new Build();
var entry = new BuildEntry { Build = emptyBuild, Name = name, PreviousName = string.Empty };
this.SaveBuild(entry);
return entry;
}
public void SaveBuild(BuildEntry buildEntry)
{
var encodedBuild = this.EncodeTemplate(buildEntry.Build);
if (string.IsNullOrWhiteSpace(buildEntry.PreviousName))
{
File.Delete($"{BuildsPath}\\{buildEntry.PreviousName}.txt");
}
File.WriteAllText($"{BuildsPath}\\{buildEntry.Name}.txt", encodedBuild);
}
public void RemoveBuild(BuildEntry buildEntry)
{
if (File.Exists($"{BuildsPath}\\{buildEntry.Name}.txt"))
{
File.Delete($"{BuildsPath}\\{buildEntry.Name}.txt");
}
if (File.Exists($"{BuildsPath}\\{buildEntry.PreviousName}.txt"))
{
File.Delete($"{BuildsPath}\\{buildEntry.PreviousName}.txt");
}
}
public IEnumerable<BuildEntry> GetBuilds()
{
foreach (var file in Directory.GetFiles(BuildsPath))
{
Build build = default;
try
{
build = this.DecodeTemplate(File.ReadAllText(file));
}
catch(Exception e)
{
this.logger.LogError($"Failed to decode template from file {file}. Details: {e}");
}
if (build is not null)
{
yield return new BuildEntry { Build = build, Name = Path.GetFileNameWithoutExtension(file), PreviousName = Path.GetFileNameWithoutExtension(file) };
}
}
}
public Build DecodeTemplate(string template)
{
return this.DecodeTemplateInner(template);
}
public string EncodeTemplate(Build build)
{
return this.EncodeTemplateInner(build);
}
private Build DecodeTemplateInner(string template)
{
this.logger.LogInformation("Attempting to decode template");
var buildMetadata = ParseEncodedTemplate(template);
this.logger.LogInformation("Decoded template. Beginning parsing");
if (buildMetadata.VersionNumber != 0)
{
this.logger.LogError($"Expected version number to be 0 but found {buildMetadata.VersionNumber}");
throw new InvalidOperationException($"Failed to parse template");
}
var build = new Build()
{
BuildMetadata = buildMetadata,
Skills = new()
};
if (Profession.TryParse(buildMetadata.PrimaryProfessionId, out var primaryProfession) is false)
{
this.logger.LogError($"Failed to parse profession with id {buildMetadata.PrimaryProfessionId}");
throw new InvalidOperationException($"Failed to parse template");
}
build.Primary = primaryProfession;
if (Profession.TryParse(buildMetadata.SecondaryProfessionId, out var secondaryProfession) is false)
{
this.logger.LogError($"Failed to parse profession with id {buildMetadata.SecondaryProfessionId}");
throw new InvalidOperationException($"Failed to parse template");
}
build.Secondary = secondaryProfession;
for(int i = 0; i < buildMetadata.AttributeCount; i++)
{
if (Daybreak.Models.Builds.Attribute.TryParse(buildMetadata.AttributesIds[i], out var attribute) is false)
{
this.logger.LogError($"Failed to parse attribute with id {buildMetadata.AttributesIds[i]}");
throw new InvalidOperationException($"Failed to parse template");
}
build.Attributes.Add(new AttributeEntry { Attribute = attribute, Points = buildMetadata.AttributePoints[i] });
}
for(int i = 0; i < 8; i++)
{
if (Skill.TryParse(buildMetadata.SkillIds[i], out var skill) is false)
{
this.logger.LogError($"Failed to parse skill with id {buildMetadata.SkillIds[i]}");
throw new InvalidOperationException($"Failed to parse template");
}
build.Skills.Add(skill);
}
return build;
}
private string EncodeTemplateInner(Build build)
{
this.logger.LogInformation("Building build metadata");
var buildMetadata = new BuildMetadata
{
VersionNumber = 0,
NewTemplate = true,
Header = 14,
PrimaryProfessionId = build.Primary.Id,
SecondaryProfessionId = build.Secondary.Id,
AttributeCount = build.Attributes.Count,
AttributesIds = build.Attributes.Select(attrEntry => attrEntry.Attribute.Id).ToList(),
AttributePoints = build.Attributes.Select(attrEntry => attrEntry.Points).ToList(),
SkillIds = build.Skills.Select(skill => skill.Id).ToList(),
TailPresent = true
};
this.logger.LogInformation("Encoding metadata into binary");
var encodedBinary = BuildEncodedString(buildMetadata);
int index = 0;
var encodedBase64 = new List<int>();
while (index < encodedBinary.Length)
{
var subset = new string(encodedBinary.Skip(index).Take(6).ToArray());
encodedBase64.Add(FromBitString(subset));
index += 6;
}
var template = new string(encodedBase64.Select(b => DecodingLookupTable[b]).ToArray());
return template;
}
private static string ToBitString(int value)
{
var sb = new StringBuilder();
while(value > 0)
{
sb.Append(value % 2 == 1 ? 1 : 0);
value /= 2;
}
while(sb.Length < 6)
{
sb.Append(0);
}
return sb.ToString();
}
private static BuildMetadata ParseEncodedTemplate(string template)
{
var curedTemplate = template.Trim();
var buildMetadata = new BuildMetadata();
buildMetadata.Base64Decoded = template.Select(c => DecodingLookupTable.IndexOf(c)).ToList();
buildMetadata.BinaryDecoded = buildMetadata.Base64Decoded.Select(b => ToBitString(b)).ToList();
var stream = new DecodeCharStream(buildMetadata.BinaryDecoded.ToArray());
buildMetadata.Header = stream.Read(4);
if (buildMetadata.Header == 14)
{
buildMetadata.VersionNumber = stream.Read(4);
buildMetadata.NewTemplate = true;
}
else
{
buildMetadata.VersionNumber = buildMetadata.Header;
buildMetadata.NewTemplate = false;
}
buildMetadata.ProfessionIdLength = stream.Read(2) * 2 + 4;
buildMetadata.PrimaryProfessionId = stream.Read(buildMetadata.ProfessionIdLength);
buildMetadata.SecondaryProfessionId = stream.Read(buildMetadata.ProfessionIdLength);
buildMetadata.AttributeCount = stream.Read(4);
buildMetadata.AttributesLength = stream.Read(4) + 4;
for (int i = 0; i < buildMetadata.AttributeCount; i++)
{
buildMetadata.AttributesIds.Add(stream.Read(buildMetadata.AttributesLength));
buildMetadata.AttributePoints.Add(stream.Read(4));
}
buildMetadata.SkillsLength = stream.Read(4) + 8;
for (int i = 0; i < 8; i++)
{
buildMetadata.SkillIds.Add(stream.Read(buildMetadata.SkillsLength));
}
if (stream.Position < stream.Length - 1)
{
buildMetadata.TailPresent = true;
}
return buildMetadata;
}
private static string BuildEncodedString(BuildMetadata buildMetadata)
{
var stream = new EncodeCharStream();
if(buildMetadata.NewTemplate || buildMetadata.Header == 14)
{
stream.Write(14, 4);
}
stream.Write(0, 4);
var desiredProfessionIdLength = GetBitLength(new List<int> { buildMetadata.PrimaryProfessionId, buildMetadata.SecondaryProfessionId }.Max());
var professionIdLength = Math.Max((desiredProfessionIdLength - 4) / 2, 0);
var finalProfessionIdLength = professionIdLength * 2 + 4;
stream.Write(professionIdLength, 2);
stream.Write(buildMetadata.PrimaryProfessionId, finalProfessionIdLength);
stream.Write(buildMetadata.SecondaryProfessionId, finalProfessionIdLength);
stream.Write(buildMetadata.AttributeCount, 4);
var desiredAttributesLength = GetBitLength(buildMetadata.AttributesIds.Any() ? buildMetadata.AttributesIds.Max() : 0);
var attributesLength = Math.Max(desiredAttributesLength - 4, 0);
var finalAttributesLength = attributesLength + 4;
stream.Write(attributesLength, 4);
for(int i = 0; i < buildMetadata.AttributeCount; i++)
{
stream.Write(buildMetadata.AttributesIds[i], finalAttributesLength);
stream.Write(buildMetadata.AttributePoints[i], 4);
}
var desiredSkillsLength = GetBitLength(buildMetadata.SkillIds.Max());
var skillsLength = Math.Max(desiredSkillsLength - 8, 0);
var finalSkillsLength = skillsLength + 8;
stream.Write(skillsLength, 4);
for(int i = 0; i < 8; i++)
{
stream.Write(buildMetadata.SkillIds[i], finalSkillsLength);
}
if (buildMetadata.TailPresent)
{
stream.Write(0, 1);
}
return stream.GetEncodedString();
}
private static int GetBitLength(int value)
{
int decimals = 1;
value /= 2;
while (value > 0)
{
decimals++;
value /= 2;
}
return decimals;
}
private static int FromBitString(string bitString)
{
var sb = new StringBuilder(bitString);
while (sb.Length < 6)
{
sb.Append('0');
}
bitString = sb.ToString();
var value = 0d;
for (int i = 0; i < bitString.Length; i++)
{
value += bitString[i] == '1' ? Math.Pow(2, i) : 0;
}
return (int)value;
}
}
}
@@ -0,0 +1,17 @@
using Daybreak.Models.Builds;
using System.Collections.Generic;
namespace Daybreak.Services.BuildTemplates
{
public interface IBuildTemplateManager
{
bool IsTemplate(string template);
BuildEntry CreateBuild();
BuildEntry CreateBuild(string name);
void SaveBuild(BuildEntry buildEntry);
void RemoveBuild(BuildEntry buildEntry);
IEnumerable<BuildEntry> GetBuilds();
Build DecodeTemplate(string template);
string EncodeTemplate(Build build);
}
}
@@ -0,0 +1,36 @@
using System;
namespace Daybreak.Services.BuildTemplates.Models
{
public sealed class DecodeCharStream
{
private readonly string innerCharArray;
public int Length => this.innerCharArray.Length;
public int Position { get; set; }
public DecodeCharStream(string[] encodedValues)
{
this.innerCharArray = string.Join("", encodedValues);
}
public int Read(int count)
{
var value = FromEncodedBinary(this.innerCharArray, this.Position, count);
this.Position += count;
return value;
}
private static int FromEncodedBinary(string encoded, int startIndex, int count)
{
var sum = 0d;
for (int i = startIndex; i < startIndex + count; i++)
{
sum += encoded[i] == '1' ? Math.Pow(2, i - startIndex) : 0;
}
return (int)sum;
}
}
}
@@ -0,0 +1,35 @@
using System.Text;
namespace Daybreak.Services.BuildTemplates.Models
{
public sealed class EncodeCharStream
{
private readonly StringBuilder innerStringBuilder = new StringBuilder();
public void Write(int value, int count)
{
this.EncodeToBinary(value, count);
}
public string GetEncodedString()
{
return innerStringBuilder.ToString();
}
private void EncodeToBinary(int value, int count)
{
while(value > 0 && count > 0)
{
this.innerStringBuilder.Append(value % 2 == 1 ? '1' : '0');
value /= 2;
count--;
}
while (count > 0)
{
this.innerStringBuilder.Append('0');
count--;
}
}
}
}
@@ -1,5 +1,4 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using System;
@@ -15,6 +14,8 @@ namespace Daybreak.Services.Configuration
private ApplicationConfiguration applicationConfiguration;
private readonly ILogger logger;
public event EventHandler ConfigurationChanged;
public ConfigurationManager(ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
@@ -39,6 +40,7 @@ namespace Daybreak.Services.Configuration
{
File.WriteAllText(ConfigName, applicationConfiguration.Serialize());
this.applicationConfiguration = applicationConfiguration;
this.ConfigurationChanged?.Invoke(this, new EventArgs());
}
}
}
@@ -1,9 +1,11 @@
using Daybreak.Configuration;
using System;
namespace Daybreak.Services.Configuration
{
public interface IConfigurationManager
{
event EventHandler ConfigurationChanged;
ApplicationConfiguration GetConfiguration();
void SaveConfiguration(ApplicationConfiguration applicationConfiguration);
}
@@ -0,0 +1,12 @@
using Daybreak.Models.Builds;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public interface IIconRetriever
{
Task<Optional<Stream>> GetIcon(Skill skill);
}
}
@@ -0,0 +1,79 @@
using Daybreak.Models.Builds;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using HtmlAgilityPack;
using System;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconRetriever : IIconRetriever
{
private const string NamePlaceholder = "[SKILLNAME]";
private const string BaseUrl = "https://wiki.guildwars.com";
private const string QueryUrl = $"wiki/File:{NamePlaceholder}.jpg";
private readonly HttpClient httpClient = new();
private readonly ILogger logger;
public IconRetriever(
ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient.BaseAddress = new Uri(BaseUrl);
}
public async Task<Optional<Stream>> GetIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_");
var skillIconUrl = QueryUrl.Replace(NamePlaceholder, curedSkillName);
this.logger.LogInformation($"Looking up icon for skill '{skill.Name}' at url {skillIconUrl}");
using var response = await this.httpClient.GetAsync(skillIconUrl).ConfigureAwait(false);
if (response.IsSuccessStatusCode is false)
{
this.logger.LogError($"Client returned status code {response.StatusCode}");
return Optional.None<Stream>();
}
this.logger.LogInformation("Crawling through response for href to latest icon url");
var doc = new HtmlDocument();
doc.LoadHtml(await response.Content.ReadAsStringAsync());
var url = GetHref(doc);
if (url is null)
{
this.logger.LogError("Failed to find latest icon url");
return Optional.None<Stream>();
}
this.logger.LogInformation($"Found latest icon url at {BaseUrl + "/" + url}. Requesting stream");
using var iconResponse = await this.httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
this.logger.LogInformation("Retrieved latest icon stream");
return new MemoryStream(await iconResponse.Content.ReadAsByteArrayAsync());
}
this.logger.LogError($"Failed to retrieve icon from {BaseUrl + "/" + url}");
return Optional.None<Stream>();
}
private static string GetHref(HtmlDocument doc)
{
foreach (var child in doc.DocumentNode.Descendants("a"))
{
var targetAttribute = child.Attributes.Where(a => a.Name == "href" && a.Value.Contains("images")).FirstOrDefault();
if (targetAttribute is not null)
{
return targetAttribute.Value;
}
}
return null;
}
}
}
@@ -0,0 +1,11 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLifetime;
using System;
namespace Daybreak.Services.KeyboardHook
{
public interface IKeyboardHookService : IApplicationLifetimeService
{
event EventHandler<KeyboardHookEventArgs> KeyboardPressed;
}
}
@@ -0,0 +1,117 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Pepa.Wpf.Utilities;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Extensions;
using System.Linq;
using System.Runtime.InteropServices;
namespace Daybreak.Services.KeyboardHook
{
// Based on https://gist.github.com/Stasonix
// https://stackoverflow.com/questions/604410/global-keyboard-capture-in-c-sharp-application
public sealed class KeyboardHookService : IKeyboardHookService, IDisposable
{
private readonly ILogger logger;
private IntPtr windowsHookHandle;
private IntPtr user32LibraryHandle;
private NativeMethods.HookProc hookProc;
public event EventHandler<KeyboardHookEventArgs> KeyboardPressed;
public KeyboardHookService(
ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.Setup();
}
public void OnStartup()
{
}
public void OnClosing()
{
this.Dispose();
}
private void Setup()
{
this.windowsHookHandle = IntPtr.Zero;
this.hookProc = this.LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.
this.user32LibraryHandle = NativeMethods.LoadLibrary("User32");
if (this.user32LibraryHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogError($"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
this.windowsHookHandle = NativeMethods.SetWindowsHookEx(NativeMethods.WH_KEYBOARD_LL, this.hookProc, this.user32LibraryHandle, 0);
if (this.windowsHookHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogError($"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
}
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{
var handled = false;
var wparamTyped = wParam.ToInt32();
if (Enum.IsDefined(typeof(KeyboardState), wparamTyped))
{
var p = Marshal.PtrToStructure(lParam, typeof(KeyboardInput)).Cast<KeyboardInput>();
var eventArguments = new KeyboardHookEventArgs(wparamTyped.Cast<KeyboardState>(), p);
this.KeyboardPressed?.Invoke(this, eventArguments);
handled = eventArguments.Handled;
}
return handled ? (IntPtr)1 : NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
private void Dispose(bool disposing)
{
if (disposing)
{
// because we can unhook only in the same thread, not in garbage collector thread
if (this.windowsHookHandle != IntPtr.Zero)
{
if (NativeMethods.UnhookWindowsHookEx(this.windowsHookHandle) is false)
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogCritical($"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
this.windowsHookHandle = IntPtr.Zero;
this.hookProc -= LowLevelKeyboardProc;
}
}
if (this.user32LibraryHandle != IntPtr.Zero)
{
if (NativeMethods.FreeLibrary(this.user32LibraryHandle) is false) // reduces reference to library by 1.
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogCritical($"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
this.user32LibraryHandle = IntPtr.Zero;
}
}
~KeyboardHookService()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
@@ -0,0 +1,8 @@
using Daybreak.Services.ApplicationLifetime;
namespace Daybreak.Services.KeyboardMacros
{
public interface IMacroService : IApplicationLifetimeService
{
}
}
@@ -0,0 +1,118 @@
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Services.KeyboardHook;
using Daybreak.Services.Logging;
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Extensions;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Daybreak.Services.KeyboardMacros
{
public sealed class MacroService : IMacroService
{
private readonly CancellationTokenSource cancellationTokenSource = new();
private readonly ILogger logger;
private readonly IKeyboardHookService keyboardHookService;
private readonly IConfigurationManager configurationManager;
private readonly HashSet<Keys> KeysDown = new();
private IEnumerable<KeyMacro> loadedMacros;
private bool gameActive, hookEnabled;
private IntPtr gwWindowHwnd;
public MacroService(
ILogger logger,
IKeyboardHookService keyboardHookService,
IConfigurationManager configurationManager)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.keyboardHookService = keyboardHookService.ThrowIfNull(nameof(keyboardHookService));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.configurationManager.ConfigurationChanged += (s, e) => this.LoadConfiguration();
this.LoadConfiguration();
this.keyboardHookService.KeyboardPressed += this.KeyboardHookService_KeyboardPressed;
this.SetupGameActiveChecker();
}
private void LoadConfiguration()
{
this.hookEnabled = this.configurationManager.GetConfiguration().ExperimentalFeatures.CanInterceptKeys;
this.loadedMacros = this.configurationManager.GetConfiguration().ExperimentalFeatures.Macros ?? new List<KeyMacro>();
}
private void SetupGameActiveChecker()
{
TaskExtensions.RunPeriodicAsync(() =>
{
var windowHandle = NativeMethods.GetForegroundWindow();
var windowNameLength = NativeMethods.GetWindowTextLength(windowHandle);
var sb = new StringBuilder(windowNameLength);
_ = NativeMethods.GetWindowText(windowHandle, sb, windowNameLength + 1);
if (sb.ToString() != "Guild Wars")
{
this.gameActive = false;
return;
}
this.gwWindowHwnd = windowHandle;
this.gameActive = true;
},
TimeSpan.Zero,
TimeSpan.FromMilliseconds(33),
cancellationTokenSource.Token);
}
private void KeyboardHookService_KeyboardPressed(object sender, KeyboardHookEventArgs e)
{
if (this.gameActive && this.hookEnabled)
{
if (e.KeyboardState == KeyboardState.KeyDown)
{
this.KeysDown.Add(e.KeyboardInput.Key);
}
else if (e.KeyboardState == KeyboardState.KeyUp)
{
this.KeysDown.Remove(e.KeyboardInput.Key);
return;
}
e.Handled = this.loadedMacros
.Where(keyMacro => MacroContainsKey(keyMacro, e.KeyboardInput.Key))
.Where(this.MacroHit)
.Do(this.HandleMacro)
.Any();
}
}
public void OnClosing()
{
this.cancellationTokenSource.Cancel();
}
public void OnStartup()
{
}
private bool MacroHit(KeyMacro keyMacro)
{
return this.KeysDown.Intersect(keyMacro.Keys).OrderBy(key => key).SequenceEqual(keyMacro.Keys.OrderBy(key => key));
}
private void HandleMacro(KeyMacro keyMacro)
{
// TODO: Propagate key to guildwars executable.
}
private static bool MacroContainsKey(KeyMacro keyMacro, Keys lastKey)
{
return keyMacro.Keys.Contains(lastKey);
}
}
}
+9 -1
View File
@@ -1,4 +1,5 @@
using System.Windows.Controls;
using System;
using System.Windows.Controls;
namespace Daybreak.Services.ViewManagement
{
@@ -8,5 +9,12 @@ namespace Daybreak.Services.ViewManagement
void ShowView<T>()
where T : UserControl;
void ShowView<T>(object dataContext)
where T : UserControl;
void ShowView(Type type);
void ShowView(Type type, object dataContext);
}
}
+28 -3
View File
@@ -1,6 +1,7 @@
using Slim;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
namespace Daybreak.Services.ViewManagement
@@ -32,9 +33,33 @@ namespace Daybreak.Services.ViewManagement
public void ShowView<T>() where T : UserControl
{
var view = this.serviceManager.GetService<T>();
this.container.Children.Clear();
this.container.Children.Add(view);
this.ShowViewInner(typeof(T), null);
}
public void ShowView<T>(object dataContext) where T : UserControl
{
this.ShowViewInner(typeof(T), dataContext);
}
public void ShowView(Type type)
{
this.ShowViewInner(type, null);
}
public void ShowView(Type type, object dataContext)
{
this.ShowViewInner(type, dataContext);
}
private void ShowViewInner(Type viewType, object dataContext)
{
Application.Current.Dispatcher.Invoke(() =>
{
var view = this.serviceManager.GetService(viewType).As<UserControl>();
this.container.Children.Clear();
this.container.Children.Add(view);
view.DataContext = dataContext;
});
}
}
}
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Daybreak.Services.Privilege
{
public interface IPrivilegeManager
{
bool AdminPrivileges { get; }
void RequestAdminPrivileges<TCancelView>(string messageToUser, object dataContextOfCancelView = null)
where TCancelView : UserControl;
}
}

Some files were not shown because too many files have changed in this diff Show More