Compare commits

...
56 Commits
Author SHA1 Message Date
amacocianandGitHub ee9fcf38b7 Release v102 (#355)
Restrict maximum notification size
Browser detects builds in downloaded files
BuildTemplate has a button to navigate to the build source
Closes #354
Closes #353
Closes #352
2023-09-08 10:32:47 +00:00
amacocianandGitHub 5abb24bdd4 Append source url for builds loaded through the browser (#351)
Closes #350
2023-09-07 23:48:13 +00:00
amacocianandGitHub 53f17d3f22 Display event remaining time (#348)
* Event notifications display remaining time
Closes #344

* Increment version
2023-09-03 20:06:41 +00:00
amacocianandGitHub b728c38dc1 Map fixes & Npcs (#345) 2023-09-01 12:33:52 +00:00
amacocianandGitHub 5e57b6ecd0 Notification fixes (#343)
* Notification fixes
Closes #342

* Update version
2023-08-31 22:26:49 +00:00
amacocianandGitHub e9ffcdf7c3 Add search capability to logs (#341)
Closes #340
2023-08-22 13:37:18 +00:00
amacocian 118657de7c Allow CD pipeline to be run manually 2023-08-09 23:57:14 +02:00
amacocian 34c332504b Fix cd pipeline 2023-08-09 23:54:45 +02:00
amacocianandGitHub d7a29d68a1 Release v96 (#338)
Add support for non-persistent notifications
LogsView message limit
Tradechat exponential backoff
Possibility to revert to normal mode

Closes #335
Closes #322
Closes #337
2023-08-09 20:56:24 +00:00
amacocianandGitHub eefa02837d Replace Moq with NSubstitute (#336)
* Replace Moq with NSubstitute

* CI fixes
Pin .net 7.0.9 in CI
Update test dependencies

* CI fixes
2023-08-09 15:23:50 +02:00
amacocianandGitHub 5b2812fa61 Release 95 (#334)
Fix mods launching regardless of status
LogsView improvements
NotificationsView improvements
Decrease frequency of ConnectivityStatus
Guildwars array validation
Chromium build template trim selection
Build template viewer improve skill dropdown
Update dependencies

Closes #333
Closes #332
Closes #318
Closes #319
2023-08-08 15:17:16 +00:00
amacocianandGitHub e3ddfb88c1 Add ETA to update view (#331)
Closes #205
2023-08-07 13:14:52 +00:00
amacocianandGitHub 664d94aeaa PreGameContext read implementation (#328)
Fixes for memory scanner
Closes #327
Closes #326
2023-08-07 11:02:36 +00:00
amacocianandGitHub e2baf63099 Events notifications (#325)
Closes #324
2023-08-06 22:29:21 +00:00
amacocianandGitHub c1b7000139 Connection data (#317) 2023-07-24 09:44:43 +00:00
amacocianandGitHub 6e8333d76f CD fixes 2 (#314)
* CD fixes 2

* Increment version
2023-07-19 13:16:12 +03:00
amacocianandGitHub c67eddc9d4 Re-enable CD pipeline (#313) 2023-07-19 10:09:21 +00:00
amacocianandGitHub 292cf6fd94 Screen placer fixes (#312)
* Screen placer fixes
Make screen placer as a mod
Validate login data

* Remove restrictions on CD pipeline
2023-07-19 12:43:15 +03:00
amacocianandGitHub 69038bca43 Revert "Screen placer fixes (#310)" (#311)
This reverts commit f05d15ce36.
2023-07-19 12:25:36 +03:00
amacocianandGitHub f05d15ce36 Screen placer fixes (#310)
Make screen placer as a mod
Validate login data
2023-07-19 12:08:02 +03:00
amacocianandGitHub 98cb349d67 Screen placer fixes (#310)
Make screen placer as a mod
Validate login data
2023-07-19 09:06:57 +00:00
amacocianandGitHub a4314ee4d5 Permission fixes for DSOAL (#309)
* Permission fixes for DSOAL
Registry service fixes

* Show notification on fix
2023-07-16 18:08:03 +00:00
amacocianandGitHub 96097bd25a Unify UI margins (#308) 2023-07-16 16:52:20 +00:00
amacocianandGitHub 14029e6af2 Cloudflare iaum mitigation (#307)
* Cloudflare iaum mitigation
Closes #305

* Increment version
2023-07-15 11:05:27 +00:00
amacocianandGitHub 365777141d Daybreak v84 (#306)
DSOAL installation requiring admin mode
DOSAL installation overwriting existing symbollic link
DSOAL check symbollic link before launching
MenuList can be scrollable
RegisterService implementation
Notifications show date of notification
Performance improvements for picture loading
Allow metric streaming to MetricsView
Handle cloudflare blocking responses
Closes #304
Closes #303
Closes #301
Closes #300
Mitigates #305
2023-07-15 10:32:30 +00:00
amacocianandGitHub aaa5da8d9b More nightfall endgame npcs (#299) 2023-06-30 20:00:52 +00:00
amacocianandGitHub b898ab5679 Fix cache size calculation (#297) 2023-06-29 18:32:18 +00:00
amacocianandGitHub b896fa5cc7 Bugfix for image memory cache (#296)
Improved memory usage monitor
2023-06-29 16:21:19 +00:00
amacocianandGitHub bca9f6d66b Fix loading screen flickering (#295)
* Fix loading screen flickering

* Increment version
2023-06-29 15:01:41 +00:00
amacocianandGitHub d6f71cb2eb Performance improvements (#293)
Ability to disable minimap
Closes #291
Fix main player health/energy proper values
Better granularity for memory access
Focus view uses less memory footprint to gather necesarry resources
2023-06-29 13:38:28 +00:00
amacocianandGitHub 986d09867c Cache duplication fix (#292)
* Cache duplication fix
Performance improvement
Closes #290

* Increment version
2023-06-29 10:08:00 +02:00
amacocianandGitHub 97cb2151f9 Memory reader cache to better control memory access (#289)
Expand guildwars locations, campaign, continent
Make bloogum use image cache
Make bloogum show images based on character location
Link Campaign -> Continent -> Region -> Map
2023-06-28 15:32:19 +00:00
amacocianandGitHub 338ac100a1 Realm of torment npcs (#288) 2023-06-27 16:55:44 +00:00
amacocianandGitHub 01badcee6a More nightfall npcs (#287) 2023-06-27 11:26:25 +00:00
amacocianandGitHub 302cf1114c Fix bug with trader messages options (#286)
Reorder options
2023-06-26 19:05:13 +00:00
amacocianandGitHub 613e03dc75 High def skill icons (#284)
* Use high-definition skill icons

* Increment version
2023-06-26 17:42:38 +00:00
amacocianandGitHub 4267359cb2 DSOAL support (#282)
Reworked mod support
2023-06-26 13:16:29 +00:00
amacocianandGitHub daac8ec787 More nightfall npcs (#281)
Fixes for quests and npcs
UTs for quests, npcs and items
2023-06-26 10:00:17 +00:00
amacocianandGitHub 857adec459 More vabbian npcs (#280)
* More vabbian npcs

* Increment version
2023-06-23 08:50:56 +00:00
amacocianandGitHub b46e10a1a6 Vabbian npcs (#279) 2023-06-22 12:50:18 +00:00
amacocianandGitHub 10799b7e9c Fix modifiers view (#278)
Show modifiers for unknown items
Fix enemy drawing
Added some npcs
2023-06-21 20:19:44 +00:00
amacocianandGitHub 6bbeaa1738 More Kournan npcs (#277) 2023-06-21 17:36:43 +00:00
amacocianandGitHub 1a0f1cbf9a Performance improvements to images (#275) 2023-06-21 09:20:23 +00:00
amacocianandGitHub 68e708fe96 Price history improvements (#274)
Inventory view improvements
Base for inscriptions
Base for runes
2023-06-20 00:32:28 +00:00
amacocianandGitHub a2166f2327 Add Guild Hall npcs (#272) 2023-06-18 15:24:23 +00:00
amacocianandGitHub 6d55366bdc Mark unknown entities with a question mark (#271) 2023-06-18 11:58:39 +00:00
amacocianandGitHub 28fcaa81cf Installer fix (#270)
Closes #269
2023-06-17 18:52:01 +00:00
amacocianandGitHub c5efc2990e Guildwars copy service (#268)
Fix one focus view bug
2023-06-17 14:18:35 +02:00
amacocianandGitHub 597010b9a1 Memory reader base pointer fixes (#267)
Installer fixes
2023-06-16 16:17:20 +00:00
amacocianandGitHub 1f71816256 Make regex trade searches case-insensitive (#264) 2023-06-16 13:09:37 +00:00
amacocianandGitHub 7c3618d2b5 Trade alerts (#263)
Notifications improvements
2023-06-16 11:06:18 +00:00
amacocianandGitHub df9038039c Highlight color specific trading words (#262) 2023-06-09 10:13:36 +00:00
amacocianandGitHub 9a4b23976a Release v56 (#259)
Inventory Component toggle
Fix inventory component visibility
Inventory component performance improvements
2023-06-08 21:33:13 +00:00
amacocianandGitHub 8732e0fe5e Release v55 (#256)
Sounds
Split FocusView into components
Focus view inventory component
Notifications view
Internet connectivity
2023-06-08 19:58:26 +00:00
amacocianandGitHub c23b84c4d5 Improve pathfinding algorithm (#255)
Improve minimap color contrasts
2023-06-01 20:56:49 +00:00
amacocianandGitHub 345b6a792a Color coded boss icons based on primary profession (#254) 2023-05-30 22:00:00 +00:00
368 changed files with 15836 additions and 4761 deletions
+1 -4
View File
@@ -12,6 +12,7 @@ on:
paths:
- "Daybreak/**"
- "Daybreak.Installer/**"
workflow_dispatch:
jobs:
@@ -48,10 +49,6 @@ jobs:
env:
LatestReleaseTag: ${{steps.getLatestTag.outputs.tag}}
- name: Print changelog
run: |
echo "${{ env.Changelog }}"
- name: Install .NET Core
uses: actions/setup-dotnet@v3
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
env:
Solution_Path: Daybreak.sln
Test_Project_Path: Daybreak.Tests\Daybreak.Tests.csproj
Wpf_Project_Path: Daybreak\Daybreal.csproj
Wpf_Project_Path: Daybreak\Daybreak.csproj
Actions_Allow_Unsecure_Commands: true
steps:
+1 -1
View File
@@ -59,7 +59,7 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.x'
dotnet-version: '7.x'
- name: Autobuild
uses: github/codeql-action/autobuild@v2
+18 -13
View File
@@ -14,6 +14,12 @@ if (File.Exists(tempFile) is false)
return;
}
while (Process.GetProcesses().Where(p => p.ProcessName == "Daybreak").FirstOrDefault()?.HasExited is false)
{
Console.WriteLine($"Detected Daybreak process is still running. Waiting 5s and retrying");
await Task.Delay(5000);
}
Console.WriteLine("Unpacking files...");
try
{
@@ -23,32 +29,31 @@ catch
{
}
Console.WriteLine("Deleting package");
try
{
File.Delete(tempFile);
}
catch(Exception e)
{
Console.WriteLine($"Failed to delete {tempFile}.\n{e}");
}
Console.WriteLine("Deleting browser caches");
try
{
Directory.Delete("BrowserData", true);
}
catch(Exception e)
catch(Exception)
{
Console.WriteLine($"Failed to delete BrowserData.\n{e}");
}
try
{
Directory.Delete("Daybreak.exe.WebView2", true);
}
catch(Exception e)
catch(Exception)
{
Console.WriteLine($"Failed to delete Daybreak.exe.WebView2.\n{e}");
}
Console.WriteLine("Deleting package");
try
{
File.Delete(tempFile);
}
catch (Exception e)
{
Console.WriteLine($"Failed to delete {tempFile}.\n{e}");
}
Console.WriteLine("Launching application");
+14 -7
View File
@@ -1,18 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<IsPackable>false</IsPackable>
<Platforms>AnyCPU;x86;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0-preview-23371-04" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NSubstitute" Version="5.0.0" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.16">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
+15
View File
@@ -0,0 +1,15 @@
using Daybreak.Models.Guildwars;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Extensions;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Daybreak.Tests.Models;
[TestClass]
public sealed class InscriptionTests : ItemTestsBase<Inscription>
{
protected override IEnumerable<Inscription> AllItems { get; } = Inscription.Inscriptions;
}
+74
View File
@@ -0,0 +1,74 @@
using Daybreak.Models.Guildwars;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Text;
using System;
using System.Linq;
namespace Daybreak.Tests.Models;
[TestClass]
public sealed class ItemBaseTests
{
[TestMethod]
public void TryParse_UnknownId_Fails()
{
var found = ItemBase.TryParse(int.MaxValue, modifiers: default, out var item);
found.Should().BeFalse();
item.Should().BeNull();
}
[TestMethod]
public void Parse_UnknownId_ThrowsInvalidOperationException()
{
var action = () => ItemBase.Parse(int.MaxValue, modifiers: default);
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TryParse_KnownId_Succeeds()
{
var found = ItemBase.TryParse(Inscription.WeaponInscription.Id, Inscription.WeaponInscription.Modifiers, out var inscription);
found.Should().BeTrue();
inscription.Should().Be(Inscription.WeaponInscription);
}
[TestMethod]
public void Parse_KnownId_ReturnsExpected()
{
var inscription = ItemBase.Parse(Inscription.WeaponInscription.Id, Inscription.WeaponInscription.Modifiers);
inscription.Should().Be(Inscription.WeaponInscription);
}
[TestMethod]
public void ItemList_Should_ContainOnlyUniqueItems()
{
var itemMap = new Dictionary<ItemBase, int>();
foreach (var item in ItemBase.AllItems)
{
if (!itemMap.TryGetValue(item, out _))
{
itemMap[item] = 0;
}
itemMap[item] += 1;
}
var errorMessage = new StringBuilder("Duplicated item entries: ");
foreach (var tuple in itemMap)
{
if (tuple.Value > 1)
{
errorMessage.Append(tuple.Key.Name);
errorMessage.Append(", ");
}
}
if (itemMap.Any(kvp => kvp.Value > 1))
{
Assert.Fail(errorMessage.ToString());
}
}
}
+78
View File
@@ -0,0 +1,78 @@
using Daybreak.Models.Guildwars;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Extensions;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Daybreak.Tests.Models;
public abstract class ItemTestsBase<T>
where T : ItemBase
{
protected abstract IEnumerable<T> AllItems { get; }
[TestMethod]
public void InscriptionList_Should_ContainOnlyUniqueItems()
{
var itemMap = new Dictionary<T, int>();
foreach (var item in this.AllItems)
{
if (!itemMap.TryGetValue(item, out _))
{
itemMap[item] = 0;
}
itemMap[item] += 1;
}
var errorMessage = new StringBuilder($"Duplicated {typeof(T).Name} entries: ");
foreach (var tuple in itemMap)
{
if (tuple.Value > 1)
{
errorMessage.Append(tuple.Key.Name);
errorMessage.Append(", ");
}
}
if (itemMap.Any(kvp => kvp.Value > 1))
{
Assert.Fail(errorMessage.ToString());
}
}
[TestMethod]
public void InscriptionList_Should_ContainAllNpcs()
{
var itemMap = new Dictionary<T, bool>();
var definedItemFields = typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.FieldType == typeof(T));
foreach (var field in definedItemFields)
{
var item = field.GetValue(default).Cast<T>();
itemMap[item] = false;
}
foreach (var item in this.AllItems)
{
itemMap[item] = true;
}
var errorMessage = new StringBuilder($"{typeof(T).Name} entries not in list: ");
foreach (var tuple in itemMap)
{
if (tuple.Value is false)
{
errorMessage.Append(tuple.Key.Name);
errorMessage.Append(", ");
}
}
if (itemMap.Any(kvp => kvp.Value is false))
{
Assert.Fail(errorMessage.ToString());
}
}
}
+11
View File
@@ -0,0 +1,11 @@
using Daybreak.Models.Guildwars;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace Daybreak.Tests.Models;
[TestClass]
public sealed class MaterialTests : ItemTestsBase<Material>
{
protected override IEnumerable<Material> AllItems { get; } = Material.All;
}
+108
View File
@@ -0,0 +1,108 @@
using Daybreak.Models.Guildwars;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Extensions;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Daybreak.Tests.Models;
[TestClass]
public sealed class NpcTests
{
[TestMethod]
public void TryParse_UnknownId_Fails()
{
var found = Npc.TryParse(int.MaxValue, out var npc);
found.Should().BeFalse();
npc.Should().BeNull();
}
[TestMethod]
public void Parse_UnknownId_ThrowsInvalidOperationException()
{
var action = () => Npc.Parse(int.MaxValue);
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TryParse_KnownId_Succeeds()
{
var found = Npc.TryParse(Npc.Kormir.Ids.FirstOrDefault(), out var npc);
found.Should().BeTrue();
npc.Should().Be(Npc.Kormir);
}
[TestMethod]
public void Parse_KnownId_ReturnsExpected()
{
var npc = Npc.Parse(Npc.Kormir.Ids.FirstOrDefault());
npc.Should().Be(Npc.Kormir);
}
[TestMethod]
public void NpcList_Should_ContainOnlyUniqueItems()
{
var npcMap = new Dictionary<string, int>();
foreach(var npc in Npc.Npcs)
{
if (!npcMap.TryGetValue(npc.Name.ToLower(), out _))
{
npcMap[npc.Name.ToLower()] = 0;
}
npcMap[npc.Name.ToLower()] += 1;
}
var errorMessage = new StringBuilder("Duplicated npc entries: ");
foreach(var tuple in npcMap)
{
if (tuple.Value > 1)
{
errorMessage.Append(tuple.Key);
errorMessage.Append(", ");
}
}
if (npcMap.Any(kvp => kvp.Value > 1))
{
Assert.Fail(errorMessage.ToString());
}
}
[TestMethod]
public void NpcList_Should_ContainAllNpcs()
{
var npcMap = new Dictionary<Npc, bool>();
var definedNpcsFields = typeof(Npc).GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.FieldType == typeof(Npc));
foreach(var field in definedNpcsFields)
{
var npc = field.GetValue(default).Cast<Npc>();
npcMap[npc] = false;
}
foreach(var npc in Npc.Npcs)
{
npcMap[npc] = true;
}
var errorMessage = new StringBuilder("Npcs not in list: ");
foreach(var tuple in npcMap)
{
if (tuple.Value is false)
{
errorMessage.Append(tuple.Key.Name);
errorMessage.Append(", ");
}
}
if (npcMap.Any(kvp => kvp.Value is false))
{
Assert.Fail(errorMessage.ToString());
}
}
}
+108
View File
@@ -0,0 +1,108 @@
using Daybreak.Models.Guildwars;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System;
using System.Linq;
using System.Extensions;
namespace Daybreak.Tests.Models;
[TestClass]
public sealed class QuestTests
{
[TestMethod]
public void TryParse_UnknownId_Fails()
{
var found = Quest.TryParse(int.MaxValue, out var quest);
found.Should().BeFalse();
quest.Should().BeNull();
}
[TestMethod]
public void Parse_UnknownId_ThrowsInvalidOperationException()
{
var action = () => Quest.Parse(int.MaxValue);
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TryParse_KnownId_Succeeds()
{
var found = Quest.TryParse(Quest.RaisuPalaceZaishenQuest.Id, out var quest);
found.Should().BeTrue();
quest.Should().Be(Quest.RaisuPalaceZaishenQuest);
}
[TestMethod]
public void Parse_KnownId_ReturnsExpected()
{
var quest = Quest.Parse(Quest.RaisuPalaceZaishenQuest.Id);
quest.Should().Be(Quest.RaisuPalaceZaishenQuest);
}
[TestMethod]
public void QuestList_Should_ContainOnlyUniqueItems()
{
var questMap = new Dictionary<int, int>();
foreach (var quest in Quest.Quests)
{
if (!questMap.TryGetValue(quest.Id, out _))
{
questMap[quest.Id] = 0;
}
questMap[quest.Id] += 1;
}
var errorMessage = new StringBuilder("Duplicated quest entries: ");
foreach (var tuple in questMap)
{
if (tuple.Value > 1)
{
errorMessage.Append(tuple.Key);
errorMessage.Append(", ");
}
}
if (questMap.Any(kvp => kvp.Value > 1))
{
Assert.Fail(errorMessage.ToString());
}
}
[TestMethod]
public void QuestList_Should_ContainAllNpcs()
{
var questMap = new Dictionary<Quest, bool>();
var definedNpcsFields = typeof(Quest).GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.FieldType == typeof(Quest));
foreach (var field in definedNpcsFields)
{
var quest = field.GetValue(default).Cast<Quest>();
questMap[quest] = false;
}
foreach (var quest in Quest.Quests)
{
questMap[quest] = true;
}
var errorMessage = new StringBuilder("Quests not in list: ");
foreach (var tuple in questMap)
{
if (tuple.Value is false)
{
errorMessage.Append(tuple.Key.Name);
errorMessage.Append(", ");
}
}
if (questMap.Any(kvp => kvp.Value is false))
{
Assert.Fail(errorMessage.ToString());
}
}
}
+11
View File
@@ -0,0 +1,11 @@
using Daybreak.Models.Guildwars;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace Daybreak.Tests.Models;
[TestClass]
public sealed class RuneTests : ItemTestsBase<Rune>
{
protected override IEnumerable<Rune> AllItems { get; } = Rune.Runes;
}
@@ -4,7 +4,7 @@ using Daybreak.Services.BuildTemplates;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using NSubstitute;
using System.Collections.Generic;
namespace Daybreak.Tests.Services;
@@ -18,7 +18,7 @@ public class BuildTemplateManagerTests
[TestInitialize]
public void Initialize()
{
this.buildTemplateManager = new BuildTemplateManager(new Mock<ILogger<BuildTemplateManager>>().Object);
this.buildTemplateManager = new BuildTemplateManager(Substitute.For<ILogger<BuildTemplateManager>>());
}
[TestMethod]
@@ -0,0 +1,15 @@
using Daybreak.Attributes;
using Newtonsoft.Json;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "DSOAL")]
public sealed class DSOALOptions
{
[JsonProperty(nameof(Path))]
[OptionName(Name = "Path", Description = "The path to the DSOAL installation")]
public string? Path { get; set; }
[JsonProperty(nameof(Enabled))]
[OptionName(Name = "Enabled", Description = "If true, the launcher will also launch DSOAL when launching GuildWars")]
public bool Enabled { get; set; }
}
@@ -0,0 +1,10 @@
using Daybreak.Attributes;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "Event Notifier")]
public sealed class EventNotifierOptions
{
[OptionName(Name = "Enabled", Description = "If set to true, Daybreak will notify the user of any event on startup")]
public bool Enabled { get; set; } = true;
}
@@ -11,14 +11,11 @@ public sealed class FocusViewOptions
[OptionName(Name = "Enabled", Description = "If true, the focus view is enabled, showing live information from the game")]
public bool Enabled { get; set; }
[JsonProperty(nameof(MemoryReaderFrequency))]
[OptionRange<double>(MinValue = 0, MaxValue = 1000)]
[OptionName(Name = "Memory Reader Frequency", Description = "Measured in ms. Sets how often should the launcher poll information from the game. Actual frequency is capped by the memory reading speed")]
public double MemoryReaderFrequency { get; set; } = 0;
[OptionName(Name = "Inventory Component Enabled", Description = "If true, the focus view will show a component with the inventory contents")]
public bool InventoryComponentVisible { get; set; }
[JsonProperty(nameof(EnablePathfinding))]
[OptionName(Name = "Enable Pathfinding", Description = "If true, the mini-map will attempt to produce paths from the player position to objectives")]
public bool EnablePathfinding { get; set; } = true;
[OptionName(Name = "Minimap Component Enabled", Description = "If true, the focus view will show a minimap component")]
public bool MinimapComponentVisible { get; set; }
[JsonProperty(nameof(ExperienceDisplay))]
[OptionName(Name = "Experience Display Mode", Description = "Sets how should the experience display show the information")]
@@ -0,0 +1,11 @@
using Daybreak.Attributes;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "Memory Cache")]
public sealed class ImageCacheOptions
{
[OptionName(Name = "Image Cache Limit", Description = "The maximum number of MBs that will be used to cache images in memory")]
[OptionRange<double>(MinValue = 0d, MaxValue = 1000d)]
public double MemoryImageCacheLimit { get; set; } = 100;
}
@@ -0,0 +1,13 @@
using Daybreak.Attributes;
using Newtonsoft.Json;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "Memory Reader")]
public sealed class MemoryReaderOptions
{
[JsonProperty(nameof(MemoryReaderFrequency))]
[OptionRange<double>(MinValue = 0, MaxValue = 1000)]
[OptionName(Name = "Memory Reader Frequency", Description = "Measured in ms. Sets how often should the launcher polls information from the game. Actual frequency is capped by the memory reading speed")]
public double MemoryReaderFrequency { get; set; } = 0;
}
@@ -0,0 +1,10 @@
using Daybreak.Attributes;
using Daybreak.Services.Notifications.Models;
namespace Daybreak.Configuration.Options;
[OptionsIgnore]
internal sealed class NotificationStorageOptions : ILiteCollectionOptions<NotificationDTO>
{
public string CollectionName => "notifications";
}
@@ -0,0 +1,16 @@
using Daybreak.Attributes;
using Newtonsoft.Json;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "Pathfinding")]
public sealed class PathfindingOptions
{
[JsonProperty(nameof(EnablePathfinding))]
[OptionName(Name = "Enable Pathfinding", Description = "If true, the pathfinder will attempt to produce paths from the player position to objectives")]
public bool EnablePathfinding { get; set; } = true;
[JsonProperty(nameof(ImprovedPathfinding))]
[OptionName(Name = "Distance-Based Pathfinding", Description = "If true, the pathfinder will attempt use an improved algorithm to find paths. Slower but generally more precise")]
public bool ImprovedPathfinding { get; set; } = true;
}
@@ -15,5 +15,5 @@ public sealed class PriceHistoryOptions : ILiteCollectionOptions<TraderQuoteDTO>
public TimeSpan UpdateInterval { get; set; } = TimeSpan.FromHours(1);
public Dictionary<int, DateTime> ItemHistoryMetadata { get; set; } = new();
public Dictionary<string, DateTime> ItemHistoryMetadata { get; set; } = new();
}
@@ -0,0 +1,10 @@
using Daybreak.Attributes;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "Sounds")]
public sealed class SoundOptions
{
[OptionName(Name = "Sounds Enabled", Description = "If enabled, the client will play sounds.")]
public bool Enabled { get; set; }
}
@@ -0,0 +1,14 @@
using Daybreak.Attributes;
using Daybreak.Models.Trade;
using System;
using System.Collections.Generic;
namespace Daybreak.Configuration.Options;
[OptionsIgnore]
public sealed class TradeAlertingOptions
{
public TimeSpan MaxLookbackPeriod { get; set; } = TimeSpan.FromDays(31);
public DateTime LastCheckTime { get; set; } = DateTime.MinValue;
public List<TradeAlert> Alerts { get; set; } = new();
}
@@ -0,0 +1,10 @@
using Daybreak.Attributes;
using Daybreak.Services.TradeChat.Models;
namespace Daybreak.Configuration.Options;
[OptionsIgnore]
public sealed class TraderMessagesOptions : ILiteCollectionOptions<TraderMessageDTO>
{
public string CollectionName => "trader_messages";
}
+107 -6
View File
@@ -55,17 +55,35 @@ using Daybreak.Views.Trade;
using System.Net.WebSockets;
using Daybreak.Utils;
using Daybreak.Services.Notifications;
using Daybreak.Models.Trade;
using Microsoft.Extensions.Options;
using Daybreak.Services.TradeChat.Models;
using Daybreak.Services.Charts;
using Daybreak.Services.Images;
using Daybreak.Services.Drawing.Modules.Bosses;
using Daybreak.Services.InternetChecker;
using System;
using Daybreak.Services.Sounds;
using Daybreak.Services.Notifications.Models;
using Daybreak.Models.Notifications.Handling;
using Daybreak.Services.TradeChat.Notifications;
using Daybreak.Views.Copy;
using Daybreak.Services.DSOAL;
using Daybreak.Services.Mods;
using Daybreak.Views.Onboarding.DSOAL;
using Daybreak.Services.Registry;
using Daybreak.Services.DSOAL.Actions;
using Daybreak.Services.Events;
using System.Reflection;
using Daybreak.Controls;
namespace Daybreak.Configuration;
public static class ProjectConfiguration
{
private const string DaybreakUserAgent = "Daybreak";
private const string ChromeImpersonationUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.79";
public static string? CurrentConfiguration => typeof(ProjectConfiguration).Assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()?.Configuration;
public static void RegisterResolvers(IServiceManager serviceManager)
{
@@ -81,7 +99,7 @@ public static class ProjectConfiguration
.Build()
.RegisterHttpClient<BloogumClient>()
.WithMessageHandler(SetupLoggingAndMetrics<BloogumClient>)
.WithDefaultRequestHeadersSetup(SetupDaybreakUserAgent)
.WithDefaultRequestHeadersSetup(SetupChromeImpersonationUserAgent)
.Build()
.RegisterHttpClient<GraphClient>()
.WithMessageHandler(SetupLoggingAndMetrics<GraphClient>)
@@ -110,6 +128,14 @@ public static class ProjectConfiguration
.RegisterHttpClient<PriceHistoryService>()
.WithMessageHandler(SetupLoggingAndMetrics<PriceHistoryService>)
.WithDefaultRequestHeadersSetup(SetupDaybreakUserAgent)
.Build()
.RegisterHttpClient<InternetCheckingService>()
.WithMessageHandler(SetupLoggingAndMetrics<InternetCheckingService>)
.WithDefaultRequestHeadersSetup(SetupDaybreakUserAgent)
.Build()
.RegisterHttpClient<ChromiumBrowserWrapper>()
.WithMessageHandler(SetupLoggingAndMetrics<ChromiumBrowserWrapper>)
.WithDefaultRequestHeadersSetup(SetupDaybreakUserAgent)
.Build();
}
@@ -154,8 +180,16 @@ public static class ProjectConfiguration
services.AddSingleton<IThemeManager, ThemeManager>();
services.AddSingleton<INotificationService, NotificationService>();
services.AddSingleton<INotificationProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().As<NotificationService>()!);
services.AddSingleton<INotificationHandlerProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().As<NotificationService>()!);
services.AddSingleton<ILiveChartInitializer, LiveChartInitializer>();
services.AddSingleton<IImageCache, ImageCache>();
services.AddSingleton<ISoundService, SoundService>();
services.AddSingleton<IInternetCheckingService, InternetCheckingService>();
services.AddSingleton<IConnectivityStatus, ConnectivityStatus>();
services.AddSingleton<INotificationStorage, NotificationStorage>();
services.AddSingleton<ITradeAlertingService, TradeAlertingService>();
services.AddSingleton<IModsManager, ModsManager>();
services.AddSingleton<IGuildwarsMemoryCache, GuildwarsMemoryCache>();
services.AddScoped<ICredentialManager, CredentialManager>();
services.AddScoped<IApplicationLauncher, ApplicationLauncher>();
services.AddScoped<IScreenshotProvider, ScreenshotProvider>();
@@ -178,13 +212,17 @@ public static class ProjectConfiguration
services.AddScoped<IPathfinder, StupidPathfinder>();
services.AddScoped<IDrawingService, DrawingService>();
services.AddScoped<IDrawingModuleProducer, DrawingService>(sp => sp.GetRequiredService<IDrawingService>().As<DrawingService>()!);
services.AddScoped<IUModService, UModService>();
services.AddScoped<IToolboxService, ToolboxService>();
services.AddScoped<ITradeChatService<KamadanTradeChatOptions>, TradeChatService<KamadanTradeChatOptions>>();
services.AddScoped<ITradeChatService<AscalonTradeChatOptions>, TradeChatService<AscalonTradeChatOptions>>();
services.AddScoped<ITraderQuoteService, TraderQuoteService>();
services.AddScoped<IPriceHistoryDatabase, PriceHistoryDatabase>();
services.AddScoped<IPriceHistoryService, PriceHistoryService>();
services.AddScoped<IWordHighlightingService, WordHighlightingService>();
services.AddScoped<ITradeHistoryDatabase, TradeHistoryDatabase>();
services.AddScoped<IGuildwarsCopyService, GuildwarsCopyService>();
services.AddScoped<IItemHashService, ItemHashService>();
services.AddScoped<IRegistryService, RegistryService>();
services.AddScoped<IEventNotifierService, EventNotifierService>();
}
public static void RegisterViews(IViewProducer viewProducer)
@@ -200,6 +238,7 @@ public static class ProjectConfiguration
viewProducer.RegisterView<BuildTemplateView>();
viewProducer.RegisterView<BuildsListView>();
viewProducer.RegisterView<RequestElevationView>();
viewProducer.RegisterView<RequestDelevationView>();
viewProducer.RegisterView<ScreenChoiceView>();
viewProducer.RegisterView<VersionManagementView>();
viewProducer.RegisterView<LogsView>();
@@ -223,6 +262,17 @@ public static class ProjectConfiguration
viewProducer.RegisterView<AscalonTradeChatView>();
viewProducer.RegisterView<PriceQuotesView>();
viewProducer.RegisterView<PriceHistoryView>();
viewProducer.RegisterView<NotificationsView>();
viewProducer.RegisterView<TradeAlertsView>();
viewProducer.RegisterView<TradeAlertSetupView>();
viewProducer.RegisterView<TradeNotificationView>();
viewProducer.RegisterView<GuildwarsCopySelectionView>();
viewProducer.RegisterView<GuildwarsCopyView>();
viewProducer.RegisterView<DSOALInstallingView>();
viewProducer.RegisterView<DSOALOnboardingEntryView>();
viewProducer.RegisterView<DSOALSwitchView>();
viewProducer.RegisterView<DSOALHomepageView>();
viewProducer.RegisterView<DSOALBrowserView>();
}
public static void RegisterStartupActions(IStartupActionProducer startupActionProducer)
@@ -230,6 +280,7 @@ public static class ProjectConfiguration
startupActionProducer.ThrowIfNull();
startupActionProducer.RegisterAction<RenameInstallerAction>();
startupActionProducer.RegisterAction<FixSymbolicLinkStartupAction>();
}
public static void RegisterPostUpdateActions(IPostUpdateActionProducer postUpdateActionProducer)
@@ -245,9 +296,22 @@ public static class ProjectConfiguration
drawingModuleProducer.RegisterDrawingModule<NeutralEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<AlliedCreatureEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<AllyEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<BossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<NeutralBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<WarriorBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<RangerBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<MesmerBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<NecromancerBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<MonkBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<ElementalistBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<AssassinBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<RitualistBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<DervishBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<ParagonBossEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<EnemyEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<NpcEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<UnknownEntityDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<ResurrectionShrineDrawingModule>();
drawingModuleProducer.RegisterDrawingModule<CollectorDrawingModule>();
@@ -276,25 +340,56 @@ public static class ProjectConfiguration
{
optionsProducer.ThrowIfNull();
optionsProducer.RegisterOptions<LauncherOptions>();
optionsProducer.RegisterOptions<SoundOptions>();
optionsProducer.RegisterOptions<ThemeOptions>();
optionsProducer.RegisterOptions<BrowserOptions>();
optionsProducer.RegisterOptions<BuildSynchronizationOptions>();
optionsProducer.RegisterOptions<FocusViewOptions>();
optionsProducer.RegisterOptions<LauncherOptions>();
optionsProducer.RegisterOptions<MemoryReaderOptions>();
optionsProducer.RegisterOptions<ImageCacheOptions>();
optionsProducer.RegisterOptions<ToolboxOptions>();
optionsProducer.RegisterOptions<UModOptions>();
optionsProducer.RegisterOptions<DSOALOptions>();
optionsProducer.RegisterOptions<ScreenManagerOptions>();
optionsProducer.RegisterOptions<KamadanTradeChatOptions>();
optionsProducer.RegisterOptions<AscalonTradeChatOptions>();
optionsProducer.RegisterOptions<LoggingOptions>();
optionsProducer.RegisterOptions<PriceHistoryOptions>();
optionsProducer.RegisterOptions<TraderQuotesOptions>();
optionsProducer.RegisterOptions<PathfindingOptions>();
optionsProducer.RegisterOptions<NotificationStorageOptions>();
optionsProducer.RegisterOptions<TradeAlertingOptions>();
optionsProducer.RegisterOptions<TraderMessagesOptions>();
optionsProducer.RegisterOptions<EventNotifierOptions>();
}
public static void RegisterLiteCollections(IServiceCollection services)
{
RegisterLiteCollection<Models.Log, LoggingOptions>(services);
RegisterLiteCollection<TraderQuoteDTO, PriceHistoryOptions>(services);
RegisterLiteCollection<NotificationDTO, NotificationStorageOptions>(services);
RegisterLiteCollection<TraderMessageDTO, TraderMessagesOptions>(services);
}
public static void RegisterNotificationHandlers(INotificationHandlerProducer notificationHandlerProducer)
{
notificationHandlerProducer.RegisterNotificationHandler<NoActionHandler>();
notificationHandlerProducer.RegisterNotificationHandler<MessageBoxHandler>();
notificationHandlerProducer.RegisterNotificationHandler<TradeMessageNotificationHandler>();
notificationHandlerProducer.RegisterNotificationHandler<FixSymbolicLinkNotificationHandler>();
}
public static void RegisterMods(IModsManager modsManager)
{
modsManager.RegisterMod<IToolboxService, ToolboxService>();
modsManager.RegisterMod<IUModService, UModService>();
modsManager.RegisterMod<IDSOALService, DSOALService>();
modsManager.RegisterMod<IGuildwarsScreenPlacer, GuildwarsScreenPlacer>();
}
private static void RegisterLiteCollection<TCollectionType, TOptionsType>(IServiceCollection services)
@@ -313,10 +408,16 @@ public static class ProjectConfiguration
httpRequestHeaders.ThrowIfNull().TryAddWithoutValidation("User-Agent", DaybreakUserAgent);
}
private static void SetupChromeImpersonationUserAgent(HttpRequestHeaders httpRequestHeaders)
{
httpRequestHeaders.ThrowIfNull().TryAddWithoutValidation("User-Agent", ChromeImpersonationUserAgent);
}
private static HttpMessageHandler SetupLoggingAndMetrics<T>(System.IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetRequiredService<ILogger<T>>();
var metricsService = serviceProvider.GetRequiredService<IMetricsService>();
return new MetricsHttpMessageHandler<T>(
metricsService,
+45
View File
@@ -0,0 +1,45 @@
<UserControl x:Class="Daybreak.Controls.BagItemContextMenu"
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:guildwars="clr-namespace:Daybreak.Models.Guildwars"
Background="{StaticResource Daybreak.Brushes.Background}"
d:DataContext="{d:DesignInstance Type=guildwars:BagItem, IsDesignTimeCreatable=True}"
BorderBrush="{StaticResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="5, 0, 5, 0">
<TextBlock
Text="{Binding Item.Name, Mode=OneWay}"
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" />
<WrapPanel>
<TextBlock
Text="Wiki: "
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" />
<TextBlock
Text="{Binding Item.WikiUrl, Mode=OneWay}"
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.Accent}"
Cursor="Hand"
MouseLeftButtonDown="WikiTextBlock_MouseLeftButtonDown"/>
</WrapPanel>
<WrapPanel>
<TextBlock
Text="Price history: "
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" />
<TextBlock
Text="Check"
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.Accent}"
Cursor="Hand"
MouseLeftButtonDown="PriceHistoryTextBlock_MouseLeftButtonDown"/>
</WrapPanel>
</StackPanel>
</UserControl>
@@ -0,0 +1,41 @@
using Daybreak.Models.Guildwars;
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls;
/// <summary>
/// Interaction logic for BagItemContextMenu.xaml
/// </summary>
public partial class BagItemContextMenu : UserControl
{
public event EventHandler<ItemBase>? PriceHistoryClicked;
public event EventHandler<ItemBase>? WikiClicked;
public BagItemContextMenu()
{
this.InitializeComponent();
}
private void WikiTextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.DataContext is not BagItem bagItem ||
bagItem.Item is not ItemBase itemBase)
{
return;
}
this.WikiClicked?.Invoke(this, itemBase);
}
private void PriceHistoryTextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.DataContext is not BagItem bagItem ||
bagItem.Item is not ItemBase itemBase)
{
return;
}
this.PriceHistoryClicked?.Invoke(this, itemBase);
}
}
@@ -1,5 +1,6 @@
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +11,8 @@ public partial class AddButton : UserControl
{
public event EventHandler? Clicked;
public ICommand? Click { get; set; }
public AddButton()
{
this.InitializeComponent();
@@ -18,5 +21,9 @@ public partial class AddButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class BackButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public BackButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class BackButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class BinButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public BinButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class BinButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -0,0 +1,59 @@
<UserControl x:Class="Daybreak.Controls.Buttons.BrowserButton"
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.Buttons"
xmlns:controls="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<local:CircularButton Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
Highlight="{StaticResource MahApps.Brushes.Accent}"
Clicked="HighlightButton_Clicked">
<local:CircularButton.Content>
<Viewbox Stretch="Fill" Width="60" Height="60">
<Grid Width="24" Height="24"
Margin="5">
<Path Data="m7.724,17a15.176,15.176 0 0 0 4.276,7a15.176,15.176 0 0 0 4.276,-7l-8.552,0z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m7,12a17.764,17.764 0 0 0 0.271,3l9.458,0a17.764,17.764 0 0 0 0.271,-3a17.764,17.764 0 0 0 -0.271,-3l-9.458,0a17.764,17.764 0 0 0 -0.271,3z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m23.606,9l-4.863,0a19.608,19.608 0 0 1 0.257,3a19.608,19.608 0 0 1 -0.257,3l4.863,0a11.618,11.618 0 0 0 0,-6z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m22.9,7a12.025,12.025 0 0 0 -7.87,-6.6a17.922,17.922 0 0 1 3.294,6.6l4.576,0z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m5,12a19.608,19.608 0 0 1 0.257,-3l-4.863,0a11.618,11.618 0 0 0 0,6l4.863,0a19.608,19.608 0 0 1 -0.257,-3z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m1.1,17a12.025,12.025 0 0 0 7.87,6.6a17.922,17.922 0 0 1 -3.294,-6.6l-4.576,0z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m15.03,23.6a12.025,12.025 0 0 0 7.87,-6.6l-4.576,0a17.922,17.922 0 0 1 -3.294,6.6z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m16.276,7a15.176,15.176 0 0 0 -4.276,-7a15.176,15.176 0 0 0 -4.276,7l8.552,0z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
<Path Data="m8.97,0.4a12.025,12.025 0 0 0 -7.87,6.6l4.576,0a17.922,17.922 0 0 1 3.294,-6.6z"
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
</Grid>
</Viewbox>
</local:CircularButton.Content>
</local:CircularButton>
</Grid>
</UserControl>
@@ -0,0 +1,30 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
/// <summary>
/// Interaction logic for CopyButton.xaml
/// </summary>
public partial class BrowserButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public BrowserButton()
{
this.InitializeComponent();
}
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class CancelButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public CancelButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class CancelButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -2,6 +2,7 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
using System.Windows.Media;
namespace Daybreak.Controls.Buttons;
@@ -10,7 +11,10 @@ namespace Daybreak.Controls.Buttons;
/// </summary>
public partial class CircularButton : UserControl
{
public event EventHandler Clicked;
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
[GenerateDependencyProperty]
private Brush highlight = default!;
@@ -39,5 +43,9 @@ public partial class CircularButton : UserControl
private void Ellipse_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
/// <summary>
@@ -9,6 +11,9 @@ public partial class CopyButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public CopyButton()
{
this.InitializeComponent();
@@ -17,5 +22,9 @@ public partial class CopyButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class FilePickerButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public FilePickerButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class FilePickerButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class HelpButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public HelpButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class HelpButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -25,7 +25,7 @@
HorizontalAlignment="{Binding ElementName=_this, Path=HorizontalContentAlignment, Mode=OneWay}"
TextWrapping="{Binding ElementName=_this, Path=TextWrapping, Mode=OneWay}"
TextAlignment="{Binding ElementName=_this, Path=TextAlignment, Mode=OneWay}"
Padding="{Binding ElementName=_this, Path=TextPadding, Mode=OneWay}"/>
Padding="{Binding ElementName=_this, Path=TextPadding, Mode=OneWay}" />
<ContentPresenter
Content="{Binding ElementName=_this, Path=ButtonContent, Mode=OneWay}"/>
<Rectangle Fill="Transparent"
@@ -13,12 +13,13 @@ public partial class HighlightButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
[GenerateDependencyProperty]
private bool highlighted;
[GenerateDependencyProperty]
private Brush highlightColor = default!;
[GenerateDependencyProperty]
private FrameworkElement innerContent = default!;
[GenerateDependencyProperty(InitialValue = "")]
private string title = string.Empty;
[GenerateDependencyProperty(InitialValue = HorizontalAlignment.Left)]
@@ -52,5 +53,9 @@ public partial class HighlightButton : UserControl
private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class HomeButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public HomeButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class HomeButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class MaximizeButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public MaximizeButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class MaximizeButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -4,8 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls.Buttons"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Daybreak.Behaviors"
xmlns:converters="clr-namespace:Daybreak.Converters"
x:Name="_this"
Cursor="Hand"
@@ -14,6 +14,9 @@ public partial class MenuButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
[GenerateDependencyProperty]
private bool highlighted;
[GenerateDependencyProperty]
@@ -45,5 +48,9 @@ public partial class MenuButton : UserControl
private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class MinusButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public MinusButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class MinusButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
+1 -1
View File
@@ -13,7 +13,7 @@
Clicked="HighlightButton_Clicked">
<local:CircularButton.Content>
<Viewbox Width="60" Height="60">
<glyphs:RefreshGlyph Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}" Margin="0, 0, 60, 60"></glyphs:RefreshGlyph>
<glyphs:RefreshGlyph Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}" Margin="0, 60, 60, 120"></glyphs:RefreshGlyph>
</Viewbox>
</local:CircularButton.Content>
</local:CircularButton>
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
/// <summary>
@@ -9,6 +11,9 @@ public partial class RefreshButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public RefreshButton()
{
this.InitializeComponent();
@@ -17,5 +22,9 @@ public partial class RefreshButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class SaveButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public SaveButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class SaveButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
/// <summary>
@@ -9,6 +11,9 @@ public partial class StarButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public StarButton()
{
this.InitializeComponent();
@@ -17,5 +22,9 @@ public partial class StarButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Buttons;
@@ -10,6 +12,9 @@ public partial class SynchronizeButton : UserControl
{
public event EventHandler? Clicked;
[GenerateDependencyProperty]
private ICommand click = default!;
public SynchronizeButton()
{
this.InitializeComponent();
@@ -18,5 +23,9 @@ public partial class SynchronizeButton : UserControl
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.Clicked?.Invoke(this, e);
if (this.Click?.CanExecute(e) is true)
{
this.Click?.Execute(e);
}
}
}
@@ -1,21 +1,20 @@
using Daybreak.Configuration.Options;
using Daybreak.Configuration;
using Daybreak.Configuration.Options;
using Daybreak.Models;
using Daybreak.Models.Browser;
using Daybreak.Models.Guildwars;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Options;
using Daybreak.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Newtonsoft.Json.Linq;
using System;
using System.Configuration;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -35,6 +34,7 @@ public partial class ChromiumBrowserWrapper : UserControl
public static readonly DependencyProperty AddressProperty =
DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(Address));
private const long MaxBuildBytes = 200;
private const string BrowserSearchPlaceholder = "[PLACEHOLDER]";
private const string BrowserSearchLink = $"https://www.google.com/search?q={BrowserSearchPlaceholder}";
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
@@ -46,11 +46,12 @@ public partial class ChromiumBrowserWrapper : UserControl
public event EventHandler<string>? FavoriteUriChanged;
public event EventHandler? MaximizeClicked;
public event EventHandler<Build>? BuildDecoded;
public event EventHandler<DownloadedBuild>? BuildDecoded;
public event EventHandler<DownloadPayload>? DownloadingFile;
public event EventHandler<string>? DownloadedFile;
private readonly Task initializationTask;
private readonly IHttpClient<ChromiumBrowserWrapper> httpClient;
private readonly ILiveOptions<BrowserOptions> liveOptions;
private readonly ILogger<ChromiumBrowserWrapper> logger;
private readonly IBuildTemplateManager buildTemplateManager;
@@ -93,17 +94,20 @@ public partial class ChromiumBrowserWrapper : UserControl
}
public ChromiumBrowserWrapper()
: this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILiveOptions<BrowserOptions>>(),
: this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IHttpClient<ChromiumBrowserWrapper>>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILiveOptions<BrowserOptions>>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBuildTemplateManager>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILogger<ChromiumBrowserWrapper>>())
{
}
public ChromiumBrowserWrapper(
IHttpClient<ChromiumBrowserWrapper> httpClient,
ILiveOptions<BrowserOptions> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
{
this.httpClient = httpClient.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
this.logger = logger.ThrowIfNull();
@@ -221,10 +225,12 @@ public partial class ChromiumBrowserWrapper : UserControl
};
this.WebBrowser.CoreWebView2.DownloadStarting += (browser, args) =>
{
if (this.CanDownloadFiles is false)
if (!this.CanDownloadFiles)
{
this.logger?.LogInformation("Downloads are disallowed. Cancelling download");
args.Cancel = true;
this.CheckForBuildFile(args.DownloadOperation.Uri);
return;
}
if (this.DownloadsDirectory.IsNullOrWhiteSpace())
@@ -250,7 +256,15 @@ public partial class ChromiumBrowserWrapper : UserControl
};
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
this.WebBrowser.WebMessageReceived += this.CoreWebView2_WebMessageReceived!;
this.WebBrowser.CoreWebView2.Settings.AreDevToolsEnabled = false;
this.WebBrowser.CoreWebView2.DOMContentLoaded += async (browser, args) =>
{
if (this.CanDownloadBuild)
{
await this.WebBrowser.CoreWebView2.ExecuteScriptAsync(Scripts.SendSelectionOnContextMenu);
}
};
this.WebBrowser.CoreWebView2.Settings.AreDevToolsEnabled = ProjectConfiguration.CurrentConfiguration == "Debug";
this.WebBrowser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
if (this.CanDownloadBuild)
{
@@ -260,6 +274,42 @@ public partial class ChromiumBrowserWrapper : UserControl
this.browserInitialized = true;
}
private async void CheckForBuildFile(string source)
{
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.CheckForBuildFile), source);
if (!this.CanDownloadBuild)
{
return;
}
if (source.IsNullOrWhiteSpace())
{
return;
}
var result = await this.httpClient.GetAsync(source);
if (result.Content.Headers.ContentLength > MaxBuildBytes)
{
this.logger.LogInformation($"Content size [{result.Content.Headers.ContentLength}] exceeds max size [{MaxBuildBytes}]");
result.Dispose();
return;
}
var content = await result.Content.ReadAsStringAsync();
if (!this.buildTemplateManager.TryDecodeTemplate(content, out var build))
{
this.logger.LogInformation("Could not decode downloaded file");
return;
}
build.SourceUrl = this.Address;
this.BuildDecoded?.Invoke(this, new DownloadedBuild
{
Build = build,
PreferredName = result.Content.Headers.ContentDisposition?.FileName
});
}
private async void RetryInitializeButton_Clicked(object sender, EventArgs e)
{
try
@@ -284,7 +334,12 @@ public partial class ChromiumBrowserWrapper : UserControl
{
if (e.Key == System.Windows.Input.Key.Enter)
{
var input = sender.As<TextBox>().Text;
var input = sender.As<TextBox>()?.Text;
if (input is null)
{
return;
}
var maybeAddress = SanitizeAddress(input);
// If input is address, navigate to address. Otherwise search for the text input in Google
if (Uri.TryCreate(maybeAddress, UriKind.Absolute, out _))
@@ -304,7 +359,7 @@ public partial class ChromiumBrowserWrapper : UserControl
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs args)
{
BrowserPayload payload = default!;
BrowserPayload? payload = default!;
try
{
payload = args.WebMessageAsJson.Deserialize<BrowserPayload>();
@@ -317,7 +372,7 @@ public partial class ChromiumBrowserWrapper : UserControl
if (payload?.Key == BrowserPayload.PayloadKeys.ContextMenu)
{
var contextMenuPayload = args.WebMessageAsJson.Deserialize<BrowserPayload<OnContextMenuPayload>>();
var maybeTemplate = contextMenuPayload.Value!.Selection;
var maybeTemplate = contextMenuPayload?.Value?.Selection?.Trim();
if (string.IsNullOrWhiteSpace(maybeTemplate))
{
return;
@@ -328,16 +383,16 @@ public partial class ChromiumBrowserWrapper : UserControl
return;
}
if (this.buildTemplateManager.IsTemplate(maybeTemplate) is false)
if (this.buildTemplateManager.TryDecodeTemplate(maybeTemplate, out var build) is false)
{
return;
}
build.SourceUrl = contextMenuPayload?.Value?.Url;
Task.Run(() =>
{
try
{
var build = this.buildTemplateManager.DecodeTemplate(maybeTemplate);
this.Dispatcher.Invoke(() =>
{
this.ContextMenu.DataContext = build;
@@ -374,7 +429,12 @@ public partial class ChromiumBrowserWrapper : UserControl
{
var build = this.ContextMenu.DataContext.As<Build>();
this.ContextMenu.IsOpen = false;
this.BuildDecoded?.Invoke(this, build);
if (build is null)
{
return;
}
this.BuildDecoded?.Invoke(this, new DownloadedBuild { Build = build, PreferredName = this.WebBrowser.CoreWebView2.DocumentTitle });
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
@@ -0,0 +1,32 @@
<UserControl x:Class="Daybreak.Controls.FocusViewComponents.CurrentMapComponent"
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.FocusViewComponents"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
Background="{DynamicResource Daybreak.Brushes.Background}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock
FontSize="18"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Margin="10"
Text="Current Map"></TextBlock>
<Rectangle
Height="1"
Fill="{DynamicResource MahApps.Brushes.ThemeForeground}"></Rectangle>
<buttons:MenuButton
Title="{Binding Path=Name, Mode=OneWay}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Cursor="Hand"
FontSize="20"
MouseLeftButtonDown="CurrentMap_MouseLeftButtonDown"></buttons:MenuButton>
</StackPanel>
</UserControl>
@@ -0,0 +1,33 @@
using Daybreak.Models.Guildwars;
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls.FocusViewComponents;
/// <summary>
/// Interaction logic for CurrentMapComponent.xaml
/// </summary>
public partial class CurrentMapComponent : UserControl
{
public event EventHandler<string>? NavigateToClicked;
public CurrentMapComponent()
{
this.InitializeComponent();
}
private void CurrentMap_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
if (this.DataContext is not Map currentMap)
{
return;
}
if (currentMap.WikiUrl is not string url)
{
return;
}
this.NavigateToClicked?.Invoke(this, url);
}
}
@@ -0,0 +1,34 @@
<UserControl x:Class="Daybreak.Controls.FocusViewComponents.CurrentQuestComponent"
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.FocusViewComponents"
xmlns:templates="clr-namespace:Daybreak.Controls.Templates"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
Background="{DynamicResource Daybreak.Brushes.Background}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock
FontSize="18"
Margin="10"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Text="Current Quest"></TextBlock>
<Rectangle
Height="1"
Fill="{DynamicResource MahApps.Brushes.ThemeForeground}"></Rectangle>
<buttons:MenuButton
Title="{Binding Path=Name, Mode=OneWay}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Cursor="Hand"
FontSize="20"
MouseLeftButtonDown="CurrentQuest_MouseLeftButtonDown"></buttons:MenuButton>
</StackPanel>
</UserControl>
@@ -0,0 +1,32 @@
using Daybreak.Models.Guildwars;
using System;
using System.Windows.Controls;
namespace Daybreak.Controls.FocusViewComponents;
/// <summary>
/// Interaction logic for CurrentQuestComponent.xaml
/// </summary>
public partial class CurrentQuestComponent : UserControl
{
public event EventHandler<string>? NavigateToClicked;
public CurrentQuestComponent()
{
this.InitializeComponent();
}
private void CurrentQuest_MouseLeftButtonDown(object _, System.Windows.Input.MouseButtonEventArgs e)
{
if (this.DataContext is not Quest quest)
{
return;
}
if (quest.WikiUrl is not string url)
{
return;
}
this.NavigateToClicked?.Invoke(this, url);
}
}
@@ -0,0 +1,166 @@
<UserControl x:Class="Daybreak.Controls.FocusViewComponents.InventoryComponent"
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.FocusViewComponents"
xmlns:templates="clr-namespace:Daybreak.Controls.Templates"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
Background="{DynamicResource Daybreak.Brushes.Background}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<StackPanel>
<templates:BagTemplate BagName="Equipped Items"
DataContext="{Binding EquippedItems, Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Backpack"
DataContext="{Binding Backpack, Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Belt Pouch"
DataContext="{Binding BeltPouch, Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Bag 1"
DataContext="{Binding Bags[0], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Bag 2"
DataContext="{Binding Bags[1], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Equipment Pack"
DataContext="{Binding EquipmentPack, Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Materials"
DataContext="{Binding MaterialStorage, Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 1"
DataContext="{Binding StoragePanes[0], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 2"
DataContext="{Binding StoragePanes[1], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 3"
DataContext="{Binding StoragePanes[2], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 4"
DataContext="{Binding StoragePanes[3], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 5"
DataContext="{Binding StoragePanes[4], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Anniversary Storage"
DataContext="{Binding StoragePanes[5], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 6"
DataContext="{Binding StoragePanes[6], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 7"
DataContext="{Binding StoragePanes[7], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 8"
DataContext="{Binding StoragePanes[8], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 9"
DataContext="{Binding StoragePanes[9], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 10"
DataContext="{Binding StoragePanes[10], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 11"
DataContext="{Binding StoragePanes[11], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 12"
DataContext="{Binding StoragePanes[12], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
<templates:BagTemplate BagName="Storage Pane 13"
DataContext="{Binding StoragePanes[13], Mode=OneWay}"
Margin="0, 0, 0, 20"
ItemWikiClicked="BagTemplate_ItemWikiClicked"
PriceHistoryClicked="BagTemplate_PriceHistoryClicked"
ItemClicked="BagTemplate_ItemClicked"/>
</StackPanel>
</ScrollViewer>
<Grid Background="{DynamicResource Daybreak.Brushes.Background}"
VerticalAlignment="Bottom"
HorizontalAlignment="Right">
<buttons:MaximizeButton Height="30" Width="30" Margin="5"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Clicked="MaximizeButton_Clicked"></buttons:MaximizeButton>
</Grid>
<Grid Background="{DynamicResource Daybreak.Brushes.Background}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Visibility="{Binding ElementName=_this,Path=BagItemMenuVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
<templates:BagItemMenu DataContext="{Binding ElementName=_this, Path=SelectedItem, Mode=OneWay}"
CloseClicked="BagItemMenu_CloseClicked"
ItemWikiClicked="BagItemMenu_ItemWikiClicked"/>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,58 @@
using Daybreak.Models.Guildwars;
using System;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls.FocusViewComponents;
/// <summary>
/// Interaction logic for InventoryComponent.xaml
/// </summary>
public partial class InventoryComponent : UserControl
{
public event EventHandler? MaximizeClicked;
public event EventHandler<ItemBase>? ItemWikiClicked;
public event EventHandler<ItemBase>? PriceHistoryClicked;
[GenerateDependencyProperty]
private bool bagItemMenuVisible;
[GenerateDependencyProperty]
private IBagContent selectedItem = default!;
public InventoryComponent()
{
this.InitializeComponent();
}
private void MaximizeButton_Clicked(object _, EventArgs e)
{
this.MaximizeClicked?.Invoke(this, e);
}
private void BagTemplate_ItemWikiClicked(object _, ItemBase e)
{
this.ItemWikiClicked?.Invoke(this, e);
}
private void BagTemplate_PriceHistoryClicked(object _, ItemBase e)
{
this.PriceHistoryClicked?.Invoke(this, e);
}
private void BagTemplate_ItemClicked(object _, IBagContent e)
{
this.BagItemMenuVisible = true;
this.SelectedItem = e;
}
private void BagItemMenu_CloseClicked(object _, EventArgs e)
{
this.BagItemMenuVisible = false;
}
private void BagItemMenu_ItemWikiClicked(object _, ItemBase e)
{
this.ItemWikiClicked?.Invoke(this, e);
}
}
@@ -0,0 +1,75 @@
<UserControl x:Class="Daybreak.Controls.FocusViewComponents.MainPlayerInformationComponent"
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.FocusViewComponents"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
Background="{DynamicResource Daybreak.Brushes.Background}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
FontSize="18"
Margin="10"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Text="{Binding Path=Name, Mode=OneWay}"
VerticalAlignment="Center"
HorizontalAlignment="Left"></TextBlock>
<buttons:HighlightButton
Grid.Column="1"
Title="Edit Build"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Margin="10, 0, 10, 0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Cursor="Hand"
FontSize="18"
MouseLeftButtonDown="EditBuild_MouseLeftButtonDown"></buttons:HighlightButton>
<buttons:HighlightButton
Grid.Column="2"
Title="Meta Builds"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Margin="10, 0, 0, 0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Cursor="Hand"
FontSize="18"
MouseLeftButtonDown="MetaBuilds_MouseLeftButtonDown"></buttons:HighlightButton>
</Grid>
<Rectangle
Height="1"
Fill="{DynamicResource MahApps.Brushes.ThemeForeground}" />
<buttons:MenuButton
Title="{Binding Path=PrimaryProfession.Name, Mode=OneWay}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Cursor="Hand"
FontSize="20"
MouseLeftButtonDown="PrimaryProfession_MouseLeftButtonDown"></buttons:MenuButton>
<buttons:MenuButton
Title="{Binding Path=SecondaryProfession.Name, Mode=OneWay}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Cursor="Hand"
FontSize="20"
MouseLeftButtonDown="SecondaryProfession_MouseLeftButtonDown"></buttons:MenuButton>
</StackPanel>
</UserControl>
@@ -0,0 +1,97 @@
using Daybreak.Launch;
using Daybreak.Models.Guildwars;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Navigation;
using Daybreak.Views;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Core.Extensions;
using System.Windows.Controls;
namespace Daybreak.Controls.FocusViewComponents;
/// <summary>
/// Interaction logic for MainPlayerInformationComponent.xaml
/// </summary>
public partial class MainPlayerInformationComponent : UserControl
{
private readonly IBuildTemplateManager buildTemplateManager;
private readonly IViewManager viewManager;
public event EventHandler<string>? NavigateToClicked;
public MainPlayerInformationComponent()
: this(
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBuildTemplateManager>(),
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IViewManager>())
{
}
public MainPlayerInformationComponent(
IBuildTemplateManager buildTemplateManager,
IViewManager viewManager)
{
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
this.viewManager = viewManager.ThrowIfNull();
this.InitializeComponent();
}
private void MetaBuilds_MouseLeftButtonDown(object _, System.Windows.Input.MouseButtonEventArgs e)
{
if (this.DataContext is not MainPlayerInformation mainPlayer)
{
return;
}
if (mainPlayer.PrimaryProfession is not null &&
mainPlayer.PrimaryProfession != Profession.None &&
mainPlayer.PrimaryProfession.BuildsUrl is string url)
{
this.NavigateToClicked?.Invoke(this, url);
}
}
private void PrimaryProfession_MouseLeftButtonDown(object _, System.Windows.Input.MouseButtonEventArgs e)
{
if (this.DataContext is not MainPlayerInformation mainPlayer)
{
return;
}
if (mainPlayer.PrimaryProfession is not null &&
mainPlayer.PrimaryProfession != Profession.None &&
mainPlayer.PrimaryProfession.WikiUrl is string url)
{
this.NavigateToClicked?.Invoke(this, url);
}
}
private void SecondaryProfession_MouseLeftButtonDown(object _, System.Windows.Input.MouseButtonEventArgs e)
{
if (this.DataContext is not MainPlayerInformation mainPlayer)
{
return;
}
if (mainPlayer.SecondaryProfession is not null &&
mainPlayer.SecondaryProfession != Profession.None &&
mainPlayer.SecondaryProfession.WikiUrl is string url)
{
this.NavigateToClicked?.Invoke(this, url);
}
}
private void EditBuild_MouseLeftButtonDown(object _, System.Windows.Input.MouseButtonEventArgs e)
{
if (this.DataContext is not MainPlayerInformation mainPlayer)
{
return;
}
if (mainPlayer.CurrentBuild is Build build)
{
var buildEntry = this.buildTemplateManager.CreateBuild();
buildEntry.Build = build;
this.viewManager.ShowView<BuildTemplateView>(buildEntry);
}
}
}
@@ -0,0 +1,294 @@
<UserControl x:Class="Daybreak.Controls.FocusViewComponents.PlayerResourcesComponent"
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.FocusViewComponents"
xmlns:controls="clr-namespace:Daybreak.Controls"
xmlns:converters="clr-namespace:Daybreak.Converters"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:BooleanToGridLengthConverter x:Key="ResourceBarGridLengthConverter" VisibleValue="*" />
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="0.1*"></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.1*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls:VerticalResourceBar
Grid.Column="1"
Grid.ColumnSpan="3"
CurrentResourceValue="{Binding ElementName=_this, Path=CurrentExperienceInLevel, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=NextLevelExperienceThreshold, Mode=OneWay}"
Text="{Binding ElementName=_this,Path=ExperienceBarText, Mode=OneWay}"
BarColor="{DynamicResource Daybreak.Brushes.Experience}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
MouseLeftButtonDown="ExperienceBar_MouseLeftButtonDown">
<controls:VerticalResourceBar.OpacityMask>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure IsClosed="True" StartPoint="5, 1">
<PathSegmentCollection>
<LineSegment Point="1, 5"></LineSegment>
<LineSegment Point="5, 9"></LineSegment>
<LineSegment Point="15, 9"></LineSegment>
<LineSegment Point="19, 5"></LineSegment>
<LineSegment Point="15, 1"></LineSegment>
</PathSegmentCollection>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</controls:VerticalResourceBar.OpacityMask>
</controls:VerticalResourceBar>
<Viewbox
Grid.Column="1"
Grid.ColumnSpan="3"
Stretch="Fill">
<Polygon Points="0, 3.94, 3.2, 8, 10.9, 8, 13.9, 3.94, 10.9, 0, 3.1, 0"
StrokeThickness="0.08"
Stroke="{DynamicResource MahApps.Brushes.ThemeForeground}"></Polygon>
</Viewbox>
<Grid
VerticalAlignment="Center"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="2"
Grid.RowSpan="2">
<Grid.OpacityMask>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure IsClosed="True" StartPoint="1, 1">
<PathSegmentCollection>
<LineSegment Point="1, 9"></LineSegment>
<LineSegment Point="21, 9"></LineSegment>
<LineSegment Point="19, 5"></LineSegment>
<LineSegment Point="21, 1"></LineSegment>
</PathSegmentCollection>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Grid.OpacityMask>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="0"
CurrentResourceValue="{Binding Path=User.User.CurrentKurzickPoints, Mode=OneWay}"
MaxResourceValue="{Binding Path=User.User.MaxKurzickPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Kurzick}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=KurzickBarText, Mode=OneWay}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
MouseLeftButtonDown="KurzickBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="1"
CurrentResourceValue="{Binding Path=User.User.CurrentLuxonPoints, Mode=OneWay}"
MaxResourceValue="{Binding Path=User.User.MaxLuxonPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Luxon}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=LuxonBarText, Mode=OneWay}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
MouseLeftButtonDown="LuxonBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="2"
CurrentResourceValue="{Binding Path=User.User.CurrentImperialPoints, Mode=OneWay}"
MaxResourceValue="{Binding Path=User.User.MaxImperialPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Imperial}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
Text="{Binding ElementName=_this, Path=ImperialBarText, Mode=OneWay}"
MouseLeftButtonDown="ImperialBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="3"
CurrentResourceValue="{Binding Path=User.User.CurrentBalthazarPoints, Mode=OneWay}"
MaxResourceValue="{Binding Path=User.User.MaxBalthazarPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Balthazar}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
Text="{Binding ElementName=_this, Path=BalthazarBarText, Mode=OneWay}"
MouseLeftButtonDown="BalthazarBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
</Grid>
<Viewbox
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="2"
Grid.RowSpan="2"
Stretch="Fill">
<Polygon Points="0, 10.1, 21.5, 10.1, 19.4, 5, 21.5, 0.1, 0, 0.1"
StrokeThickness="0.08"
Width="21.5"
Height="10.2"
Stroke="{DynamicResource MahApps.Brushes.ThemeForeground}"></Polygon>
</Viewbox>
<Grid
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Grid.Column="3"
Grid.ColumnSpan="2"
Grid.Row="2"
Grid.RowSpan="2">
<Grid.OpacityMask>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#FFFFFFFF">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure IsClosed="True" StartPoint="1, 1">
<PathSegmentCollection>
<LineSegment Point="3, 5"></LineSegment>
<LineSegment Point="1, 9"></LineSegment>
<LineSegment Point="21, 9"></LineSegment>
<LineSegment Point="21, 1"></LineSegment>
</PathSegmentCollection>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Grid.OpacityMask>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding ElementName=_this, Path=Vanquishing, Mode=OneWay, Converter={StaticResource ResourceBarGridLengthConverter}}"></RowDefinition>
<RowDefinition Height="{Binding ElementName=_this, Path=TitleActive, Mode=OneWay, Converter={StaticResource ResourceBarGridLengthConverter}}"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="0"
CurrentResourceValue="{Binding Path=Session.Session.FoesKilled, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=TotalFoes, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Vanquish}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=VanquishingText, Mode=OneWay}"
FillAlignment="Right"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
Visibility="{Binding ElementName=_this, Path=Vanquishing, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
MouseLeftButtonDown="VanquishingBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="1"
CurrentResourceValue="{Binding ElementName=_this, Path=PointsInCurrentRank, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=PointsForNextRank, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Rank}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=TitleText, Mode=OneWay}"
FillAlignment="Right"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
Visibility="{Binding ElementName=_this, Path=TitleActive, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}" />
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="2"
CurrentResourceValue="{Binding Path=Player.PlayerInformation.CurrentHealth, Mode=OneWay}"
MaxResourceValue="{Binding Path=Player.PlayerInformation.MaxHealth, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Health}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=HealthBarText, Mode=OneWay}"
FillAlignment="Right"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
MouseLeftButtonDown="HealthBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
<controls:HorizontalResourceBar
Margin="0, 2, 0, 2"
Grid.Row="3"
CurrentResourceValue="{Binding Path=Player.PlayerInformation.CurrentEnergy, Mode=OneWay}"
MaxResourceValue="{Binding Path=Player.PlayerInformation.MaxEnergy, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="{DynamicResource Daybreak.Brushes.Energy}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="14"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=EnergyBarText, Mode=OneWay}"
FillAlignment="Right"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
MouseLeftButtonDown="EnergyBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
</Grid>
<Viewbox
Grid.Column="3"
Grid.ColumnSpan="2"
Grid.Row="2"
Grid.RowSpan="2"
Stretch="Fill">
<Polygon Points="0, 10.1, 21.5, 10.1, 21.5, 0.1, 0, 0.1, 2.1, 5"
StrokeThickness="0.08"
HorizontalAlignment="Right"
Height="10.2"
Width="21.49"
Stroke="{DynamicResource MahApps.Brushes.ThemeForeground}"></Polygon>
</Viewbox>
</Grid>
</UserControl>
@@ -0,0 +1,488 @@
using Daybreak.Configuration.Options;
using Daybreak.Launch;
using Daybreak.Models;
using Daybreak.Models.Guildwars;
using Daybreak.Services.Experience;
using Microsoft.Extensions.DependencyInjection;
using System.Configuration;
using System.Core.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.FocusViewComponents;
/// <summary>
/// Interaction logic for PlayerResourcesComponent.xaml
/// </summary>
public partial class PlayerResourcesComponent : UserControl
{
private readonly IExperienceCalculator experienceCalculator;
private readonly ILiveUpdateableOptions<FocusViewOptions> liveOptions;
[GenerateDependencyProperty]
private double currentExperienceInLevel;
[GenerateDependencyProperty]
private double nextLevelExperienceThreshold;
[GenerateDependencyProperty]
private string experienceBarText = string.Empty;
[GenerateDependencyProperty]
private string luxonBarText = string.Empty;
[GenerateDependencyProperty]
private string kurzickBarText = string.Empty;
[GenerateDependencyProperty]
private string imperialBarText = string.Empty;
[GenerateDependencyProperty]
private string balthazarBarText = string.Empty;
[GenerateDependencyProperty]
private int pointsInCurrentRank;
[GenerateDependencyProperty]
private int pointsForNextRank;
[GenerateDependencyProperty]
private bool titleActive;
[GenerateDependencyProperty]
private string titleText = string.Empty;
[GenerateDependencyProperty]
private string healthBarText = string.Empty;
[GenerateDependencyProperty]
private string energyBarText = string.Empty;
[GenerateDependencyProperty]
private bool vanquishing;
[GenerateDependencyProperty]
private int totalFoes;
[GenerateDependencyProperty]
private string vanquishingText = string.Empty;
public PlayerResourcesComponent()
: this(
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IExperienceCalculator>(),
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILiveUpdateableOptions<FocusViewOptions>>())
{
}
public PlayerResourcesComponent(
IExperienceCalculator experienceCalculator,
ILiveUpdateableOptions<FocusViewOptions> liveOptions)
{
this.experienceCalculator = experienceCalculator.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.InitializeComponent();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DataContextProperty)
{
this.UpdateGameData();
}
}
private void ExperienceBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.ExperienceDisplay)
{
case Configuration.FocusView.ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax:
this.liveOptions.Value.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.TotalCurretAndTotalMax;
break;
case Configuration.FocusView.ExperienceDisplay.TotalCurretAndTotalMax:
this.liveOptions.Value.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.RemainingUntilNextLevel;
break;
case Configuration.FocusView.ExperienceDisplay.RemainingUntilNextLevel:
this.liveOptions.Value.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.Percentage;
break;
case Configuration.FocusView.ExperienceDisplay.Percentage:
this.liveOptions.Value.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateExperienceText();
}
private void LuxonBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.LuxonPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.liveOptions.Value.LuxonPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.liveOptions.Value.LuxonPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.liveOptions.Value.LuxonPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateLuxonText();
}
private void KurzickBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.KurzickPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.liveOptions.Value.KurzickPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.liveOptions.Value.KurzickPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.liveOptions.Value.KurzickPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateKurzickText();
}
private void ImperialBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.ImperialPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.liveOptions.Value.ImperialPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.liveOptions.Value.ImperialPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.liveOptions.Value.ImperialPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateImperialText();
}
private void BalthazarBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.BalthazarPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.liveOptions.Value.BalthazarPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.liveOptions.Value.BalthazarPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.liveOptions.Value.BalthazarPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateBalthazarText();
}
private void VanquishingBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.VanquishingDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.liveOptions.Value.VanquishingDisplay = Configuration.FocusView.PointsDisplay.Remaining;
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.liveOptions.Value.VanquishingDisplay = Configuration.FocusView.PointsDisplay.Percentage;
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.liveOptions.Value.VanquishingDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateVanquishingText();
}
private void HealthBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.HealthDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.liveOptions.Value.HealthDisplay = Configuration.FocusView.PointsDisplay.Remaining;
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.liveOptions.Value.HealthDisplay = Configuration.FocusView.PointsDisplay.Percentage;
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.liveOptions.Value.HealthDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateHealthText();
}
private void EnergyBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
switch (this.liveOptions.Value.EnergyDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.liveOptions.Value.EnergyDisplay = Configuration.FocusView.PointsDisplay.Remaining;
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.liveOptions.Value.EnergyDisplay = Configuration.FocusView.PointsDisplay.Percentage;
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.liveOptions.Value.EnergyDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
break;
}
this.liveOptions.UpdateOption();
this.UpdateEnergyText();
}
private void UpdateGameData()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.User?.User is null ||
mainPlayerResourceContext.Session?.Session is null ||
mainPlayerResourceContext.Player?.PlayerInformation is null)
{
return;
}
this.CurrentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
this.NextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(mainPlayerResourceContext.Player.PlayerInformation.Experience);
this.TotalFoes = (int)(mainPlayerResourceContext.Session.Session.FoesKilled + mainPlayerResourceContext.Session.Session.FoesToKill);
this.Vanquishing = mainPlayerResourceContext.Session.Session.FoesToKill + mainPlayerResourceContext.Session.Session.FoesKilled > 0U;
this.TitleActive = mainPlayerResourceContext.Player.PlayerInformation.TitleInformation is not null && mainPlayerResourceContext.Player.PlayerInformation.TitleInformation.IsValid;
if (mainPlayerResourceContext.Player.PlayerInformation.TitleInformation is TitleInformation titleInformation && titleInformation.IsValid)
{
if (titleInformation.MaxTierNumber == titleInformation.TierNumber)
{
this.PointsInCurrentRank = (int)titleInformation.CurrentPoints!;
this.PointsForNextRank = (int)titleInformation.CurrentPoints!;
}
else if (titleInformation.IsPercentage is false)
{
this.PointsInCurrentRank = (int)((uint)titleInformation.CurrentPoints! - (uint)titleInformation.PointsForCurrentRank!);
this.PointsForNextRank = (int)((uint)titleInformation.PointsForNextRank! - (uint)titleInformation.PointsForCurrentRank!);
}
else
{
this.PointsInCurrentRank = (int)(uint)titleInformation.CurrentPoints!;
this.PointsForNextRank = (int)(uint)titleInformation.PointsForNextRank!;
}
}
this.UpdateExperienceText();
this.UpdateLuxonText();
this.UpdateKurzickText();
this.UpdateImperialText();
this.UpdateBalthazarText();
this.UpdateVanquishingText();
this.UpdateHealthText();
this.UpdateEnergyText();
this.UpdateTitleText();
}
private void UpdateExperienceText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.Player?.PlayerInformation is null)
{
return;
}
switch (this.liveOptions.Value.ExperienceDisplay)
{
case Configuration.FocusView.ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax:
var currentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
var nextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(mainPlayerResourceContext.Player.PlayerInformation.Experience);
this.ExperienceBarText = $"{(int)currentExperienceInLevel} / {(int)nextLevelExperienceThreshold} XP";
break;
case Configuration.FocusView.ExperienceDisplay.TotalCurretAndTotalMax:
var currentTotalExperience = mainPlayerResourceContext.Player.PlayerInformation.Experience;
var requiredTotalExperience = this.experienceCalculator.GetTotalExperienceForNextLevel(currentTotalExperience);
this.ExperienceBarText = $"{(int)currentTotalExperience} / {(int)requiredTotalExperience} XP";
break;
case Configuration.FocusView.ExperienceDisplay.RemainingUntilNextLevel:
var remainingExperience = this.experienceCalculator.GetRemainingExperienceForNextLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
this.ExperienceBarText = $"Remaining {(int)remainingExperience} XP";
break;
case Configuration.FocusView.ExperienceDisplay.Percentage:
var currentExperienceInLevel2 = this.experienceCalculator.GetExperienceForCurrentLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
var nextLevelExperienceThreshold2 = this.experienceCalculator.GetNextExperienceThreshold(mainPlayerResourceContext.Player.PlayerInformation.Experience);
this.ExperienceBarText = $"{(int)((double)currentExperienceInLevel2 / (double)nextLevelExperienceThreshold2 * 100)}% XP";
break;
}
}
private void UpdateLuxonText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.User?.User is null)
{
return;
}
switch (this.liveOptions.Value.LuxonPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.LuxonBarText = $"{mainPlayerResourceContext.User.User.CurrentLuxonPoints} / {mainPlayerResourceContext.User.User.MaxLuxonPoints} Luxon Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.LuxonBarText = $"Remaining {mainPlayerResourceContext.User.User.MaxLuxonPoints - mainPlayerResourceContext.User.User.CurrentLuxonPoints} Luxon Points";
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.LuxonBarText = $"{(int)((double)mainPlayerResourceContext.User.User.CurrentLuxonPoints / (double)mainPlayerResourceContext.User.User.MaxLuxonPoints * 100)}% Luxon Points";
break;
}
}
private void UpdateKurzickText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.User?.User is null)
{
return;
}
switch (this.liveOptions.Value.KurzickPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.KurzickBarText = $"{mainPlayerResourceContext.User.User.CurrentKurzickPoints} / {mainPlayerResourceContext.User.User.MaxKurzickPoints} Kurzick Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.KurzickBarText = $"Remaining {mainPlayerResourceContext.User.User!.MaxKurzickPoints - mainPlayerResourceContext.User.User.CurrentKurzickPoints} Kurzick Points";
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.KurzickBarText = $"{(int)((double)mainPlayerResourceContext.User.User.CurrentKurzickPoints / (double)mainPlayerResourceContext.User.User.MaxKurzickPoints * 100)}% Kurzick Points";
break;
}
}
private void UpdateImperialText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.User?.User is null)
{
return;
}
switch (this.liveOptions.Value.ImperialPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.ImperialBarText = $"{mainPlayerResourceContext.User.User!.CurrentImperialPoints} / {mainPlayerResourceContext.User.User.MaxImperialPoints} Imperial Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.ImperialBarText = $"Remaining {mainPlayerResourceContext.User.User!.MaxImperialPoints - mainPlayerResourceContext.User.User.CurrentImperialPoints} Imperial Points";
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.ImperialBarText = $"{(int)((double)mainPlayerResourceContext.User.User!.CurrentImperialPoints / (double)mainPlayerResourceContext.User.User.MaxImperialPoints * 100)}% Imperial Points";
break;
}
}
private void UpdateBalthazarText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.User?.User is null)
{
return;
}
switch (this.liveOptions.Value.BalthazarPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.BalthazarBarText = $"{mainPlayerResourceContext.User.User.CurrentBalthazarPoints} / {mainPlayerResourceContext.User.User.MaxBalthazarPoints} Balthazar Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.BalthazarBarText = $"Remaining {mainPlayerResourceContext.User.User.MaxBalthazarPoints - mainPlayerResourceContext.User.User.CurrentBalthazarPoints} Balthazar Points";
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.BalthazarBarText = $"{(int)((double)mainPlayerResourceContext.User.User.CurrentBalthazarPoints / (double)mainPlayerResourceContext.User.User.MaxBalthazarPoints * 100)}% Balthazar Points";
break;
}
}
private void UpdateVanquishingText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.Session?.Session is null)
{
return;
}
switch (this.liveOptions.Value.VanquishingDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.VanquishingText = $"{mainPlayerResourceContext.Session.Session.FoesKilled} / {(int)this.TotalFoes} Foes Killed";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.VanquishingText = $"Remaining {mainPlayerResourceContext.Session.Session.FoesToKill} Foes";
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.VanquishingText = $"{(int)((double)mainPlayerResourceContext.Session.Session.FoesKilled / (double)this.TotalFoes * 100)}% Foes Killed";
break;
}
}
private void UpdateHealthText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.Player?.PlayerInformation is null)
{
return;
}
switch (this.liveOptions.Value.HealthDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.HealthBarText = $"{(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentHealth} / {(int)mainPlayerResourceContext.Player.PlayerInformation.MaxHealth} Health";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.HealthBarText = $"Remaining {(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentHealth} Health";
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.HealthBarText = $"{(int)(mainPlayerResourceContext.Player.PlayerInformation.CurrentHealth / mainPlayerResourceContext.Player.PlayerInformation.MaxHealth * 100)}% Health";
break;
}
}
private void UpdateEnergyText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.Player?.PlayerInformation is null)
{
return;
}
switch (this.liveOptions.Value.EnergyDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.EnergyBarText = $"{(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentEnergy} / {(int)mainPlayerResourceContext.Player.PlayerInformation.MaxEnergy} Energy";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.EnergyBarText = $"Remaining {(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentEnergy} Energy";
break;
case Configuration.FocusView.PointsDisplay.Percentage:
this.EnergyBarText = $"{(int)(mainPlayerResourceContext.Player.PlayerInformation.CurrentEnergy / mainPlayerResourceContext.Player.PlayerInformation.MaxEnergy * 100)}% Energy";
break;
}
}
private void UpdateTitleText()
{
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
mainPlayerResourceContext.Player?.PlayerInformation is null)
{
return;
}
if (mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.IsPercentage is true)
{
this.TitleText = $"{(double?)mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.CurrentPoints / 10d}% Rank Progress";
}
else
{
this.TitleText = $"{mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.CurrentPoints}/{mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.PointsForNextRank} Rank Progress";
}
}
}
@@ -0,0 +1,40 @@
<UserControl x:Class="Daybreak.Controls.FocusViewComponents.QuestLogComponent"
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.FocusViewComponents"
xmlns:templates="clr-namespace:Daybreak.Controls.Templates"
x:Name="_this"
Background="{DynamicResource Daybreak.Brushes.Background}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Grid.Row="0"
FontSize="18"
Padding="10"
Text="Quest Log"></TextBlock>
<Rectangle Height="1"
Fill="{DynamicResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Bottom"></Rectangle>
<ScrollViewer
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Grid.Row="1">
<templates:QuestLogTemplate
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Quests="{Binding Mode=OneWay}"
MapClicked="QuestLogTemplate_MapClicked"
QuestClicked="QuestLogTemplate_QuestClicked">
</templates:QuestLogTemplate>
</ScrollViewer>
</Grid>
</UserControl>
@@ -0,0 +1,37 @@
using Daybreak.Models.Guildwars;
using System;
using System.Windows.Controls;
namespace Daybreak.Controls.FocusViewComponents;
/// <summary>
/// Interaction logic for QuestLogComponent.xaml
/// </summary>
public partial class QuestLogComponent : UserControl
{
public event EventHandler<string>? NavigateToClicked;
public QuestLogComponent()
{
this.InitializeComponent();
}
private void QuestLogTemplate_MapClicked(object _, Map e)
{
if (e is null)
{
return;
}
this.NavigateToClicked?.Invoke(this, e.WikiUrl!);
}
private void QuestLogTemplate_QuestClicked(object _, Quest e)
{
if (e is null)
{
return;
}
this.NavigateToClicked?.Invoke(this, e.WikiUrl!);
}
}
@@ -0,0 +1,53 @@
<UserControl x:Class="Daybreak.Controls.FocusViewComponents.TitleInformationComponent"
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.FocusViewComponents"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
DataContextChanged="UserControl_DataContextChanged"
x:Name="_this"
Background="{DynamicResource Daybreak.Brushes.Background}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
FontSize="18"
Margin="10"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Text="Title Track"
HorizontalAlignment="Left"></TextBlock>
<buttons:HighlightButton
Grid.Column="1"
Title="{Binding Path=Title.Name, Mode=OneWay}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Cursor="Hand"
FontSize="18"
MouseLeftButtonDown="Title_MouseLeftButtonDown"></buttons:HighlightButton>
</Grid>
<Rectangle
Height="1"
Fill="{DynamicResource MahApps.Brushes.ThemeForeground}"></Rectangle>
<buttons:MenuButton
Title="{Binding ElementName=_this, Path=TitleRankName, Mode=OneWay}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Cursor="Hand"
FontSize="20"
MouseLeftButtonDown="Title_MouseLeftButtonDown"></buttons:MenuButton>
</StackPanel>
</UserControl>
@@ -0,0 +1,54 @@
using Daybreak.Models.Guildwars;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.FocusViewComponents;
/// <summary>
/// Interaction logic for TitleInformationComponent.xaml
/// </summary>
public partial class TitleInformationComponent : UserControl
{
public event EventHandler<string>? NavigateToClicked;
[GenerateDependencyProperty]
private string titleRankName = string.Empty;
public TitleInformationComponent()
{
this.InitializeComponent();
}
private void Title_MouseLeftButtonDown(object _, MouseButtonEventArgs __)
{
if (this.DataContext is not TitleInformation titleInformation ||
titleInformation.Title is not Title title ||
title.WikiUrl is not string url)
{
return;
}
this.NavigateToClicked?.Invoke(this, url);
}
private void UserControl_DataContextChanged(object _, DependencyPropertyChangedEventArgs __)
{
if (this.DataContext is not TitleInformation titleInformation)
{
return;
}
if (titleInformation.Title is not null &&
titleInformation.Title.Tiers!.Count > titleInformation.TierNumber - 1)
{
var rankIndex = (int)titleInformation.TierNumber! - 1;
this.TitleRankName = $"{titleInformation.Title.Tiers![rankIndex]} ({titleInformation.TierNumber}/{titleInformation.MaxTierNumber})";
}
else
{
this.TitleRankName = string.Empty;
}
}
}
@@ -43,7 +43,7 @@ public partial class LivingEntityContextMenu : UserControl
private void NpcDefinitionTextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
this.LivingEntityContextMenuClicked?.Invoke(this, this.DataContext as LivingEntity? ?? default);
this.LivingEntityContextMenuClicked?.Invoke(this, this.DataContext as LivingEntity ?? default);
}
private void PrimaryProfessionTextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
+1 -1
View File
@@ -19,6 +19,6 @@ public partial class MapIconContextMenu : UserControl
private void TextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
this.MapIconContextMenuClicked?.Invoke(this, this.DataContext as MapIcon? ?? default);
this.MapIconContextMenuClicked?.Invoke(this, this.DataContext as MapIcon ?? default);
}
}
+50 -9
View File
@@ -7,9 +7,16 @@
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
xmlns:glyphs="clr-namespace:Daybreak.Controls.Glyphs"
xmlns:options="clr-namespace:Daybreak.Controls.Options"
Loaded="UserControl_Loaded"
Unloaded="UserControl_Unloaded"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<StackPanel>
<local:ExpandableMenuSection
SectionTitle="Guild Wars" Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
@@ -20,28 +27,55 @@
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Height="30"
Cursor="Hand"
Clicked="GameCompanionButton_Clicked">
</buttons:MenuButton>
Clicked="GameCompanionButton_Clicked" />
<buttons:MenuButton Title="Manage builds"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Height="30"
Cursor="Hand"
Clicked="ManageBuildsButton_Clicked">
</buttons:MenuButton>
Clicked="ManageBuildsButton_Clicked" />
<buttons:MenuButton Title="Download Guild Wars"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Height="30"
Cursor="Hand"
Clicked="DownloadGuildwarsButton_Clicked">
</buttons:MenuButton>
Clicked="DownloadGuildwarsButton_Clicked" />
<buttons:MenuButton Title="Copy Guild Wars"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Height="30"
Cursor="Hand"
Clicked="CopyGuildwarsButton_Clicked" />
</local:ExpandableMenuSection.Children>
</local:ExpandableMenuSection>
<Grid>
<TextBlock FontSize="16"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="0, 0, 10, 0"
Foreground="{DynamicResource MahApps.Brushes.Accent}"
Visibility="{Binding ElementName=_this, Path=ShowingNotificationCount, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Text="{Binding ElementName=_this, Path=NotificationCount, Mode=OneWay}" />
<buttons:MenuButton FontSize="16"
Height="30"
Title="Notifications"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="0, 0, 0, 1"
Clicked="NotificationsButton_Clicked"/>
</Grid>
<local:ExpandableMenuSection
SectionTitle="Trade" Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="16">
<local:ExpandableMenuSection.Children>
<buttons:MenuButton Title="Alerts"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Height="30"
Cursor="Hand"
Clicked="TradeAlertsButton_Clicked">
</buttons:MenuButton>
<buttons:MenuButton Title="Kamadan"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
@@ -83,8 +117,15 @@
Cursor="Hand"
Clicked="ToolboxButton_Clicked">
</buttons:MenuButton>
<buttons:MenuButton Title="DSOAL"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Height="30"
Cursor="Hand"
Clicked="DSOALButton_Clicked">
</buttons:MenuButton>
</local:ExpandableMenuSection.Children>
</local:ExpandableMenuSection>
</local:ExpandableMenuSection>
<local:ExpandableMenuSection
SectionTitle="Settings" Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="16">
@@ -134,5 +175,5 @@
</local:ExpandableMenuSection.Children>
</local:ExpandableMenuSection>
</StackPanel>
</Grid>
</ScrollViewer>
</UserControl>
+76
View File
@@ -1,7 +1,10 @@
using Daybreak.Configuration.Options;
using Daybreak.Launch;
using Daybreak.Services.Navigation;
using Daybreak.Services.Notifications;
using Daybreak.Views;
using Daybreak.Views.Copy;
using Daybreak.Views.Onboarding.DSOAL;
using Daybreak.Views.Onboarding.Toolbox;
using Daybreak.Views.Onboarding.UMod;
using Daybreak.Views.Trade;
@@ -9,7 +12,11 @@ using Microsoft.Extensions.DependencyInjection;
using System;
using System.Configuration;
using System.Core.Extensions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls;
@@ -19,11 +26,20 @@ namespace Daybreak.Controls;
public partial class MenuList : UserControl
{
private readonly IViewManager viewManager;
private readonly INotificationStorage notificationStorage;
private readonly ILiveOptions<LauncherOptions> liveOptions;
private CancellationTokenSource? cancellationTokenSource;
[GenerateDependencyProperty]
private bool showingNotificationCount;
[GenerateDependencyProperty]
private int notificationCount;
public MenuList()
: this(
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IViewManager>(),
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<INotificationStorage>(),
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILiveOptions<LauncherOptions>>())
{
this.InitializeComponent();
@@ -31,9 +47,11 @@ public partial class MenuList : UserControl
private MenuList(
IViewManager viewManager,
INotificationStorage notificationStorage,
ILiveOptions<LauncherOptions> liveOptions)
{
this.viewManager = viewManager.ThrowIfNull();
this.notificationStorage = notificationStorage.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
}
@@ -77,6 +95,11 @@ public partial class MenuList : UserControl
this.viewManager.ShowView<GuildwarsDownloadView>();
}
private void CopyGuildwarsButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<GuildwarsCopySelectionView>();
}
private void UModButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<UModOnboardingEntryView>();
@@ -87,6 +110,11 @@ public partial class MenuList : UserControl
this.viewManager.ShowView<ToolboxOnboardingEntryView>();
}
private void DSOALButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<DSOALOnboardingEntryView>();
}
private void KamadanButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<KamadanTradeChatView>();
@@ -101,4 +129,52 @@ public partial class MenuList : UserControl
{
this.viewManager.ShowView<PriceQuotesView>();
}
private void NotificationsButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<NotificationsView>();
}
private void TradeAlertsButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<TradeAlertsView>();
}
private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
this.cancellationTokenSource = new CancellationTokenSource();
this.PeriodicallyCheckUnopenedNotifications(this.cancellationTokenSource.Token);
}
private void UserControl_Unloaded(object sender, System.Windows.RoutedEventArgs e)
{
this.cancellationTokenSource?.Cancel();
this.cancellationTokenSource?.Dispose();
this.cancellationTokenSource = default;
}
private async void PeriodicallyCheckUnopenedNotifications(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var unopenedNotifications = this.notificationStorage.GetPendingNotifications().ToList();
if (unopenedNotifications.Any())
{
await this.Dispatcher.InvokeAsync(() =>
{
this.ShowingNotificationCount = true;
this.NotificationCount = unopenedNotifications.Count;
});
}
else
{
await this.Dispatcher.InvokeAsync(() =>
{
this.ShowingNotificationCount = false;
});
}
await Task.Delay(5000, cancellationToken);
}
}
}
@@ -6,7 +6,6 @@
xmlns:local="clr-namespace:Daybreak.Controls.Minimap"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
xmlns:controls="clr-namespace:Daybreak.Controls"
xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
@@ -79,7 +78,9 @@
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Clicked="MaximizeButton_Clicked"></buttons:MaximizeButton>
</Grid>
<WrapPanel Background="{DynamicResource Daybreak.Brushes.Background}" VerticalAlignment="Top" HorizontalAlignment="Right" >
<WrapPanel Background="{DynamicResource Daybreak.Brushes.Background}"
VerticalAlignment="Top"
HorizontalAlignment="Right" >
<TextBlock Text="{Binding ElementName=_this, Path=TargetEntityId, Mode=OneWay}" Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
FontSize="16" Padding="5" ></TextBlock>
<TextBlock Text=" - " Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
@@ -23,8 +23,6 @@ using Daybreak.Services.Themes;
using Daybreak.Services.Metrics;
using System.Diagnostics.Metrics;
using System.Diagnostics;
using SkiaSharp;
using SkiaSharp.Views.Desktop;
namespace Daybreak.Controls.Minimap;
@@ -40,8 +38,6 @@ public partial class GuildwarsMinimap : UserControl
private const int MapDownscaleFactor = 10;
private const int EntitySize = 100;
private const float PositionRadius = 150;
// Minimum amount of milliseconds to pass between calculating paths to objectives
private const int MinPathCalculationFrequency = 500;
private readonly Histogram<double> drawingLatency;
private readonly DispatcherTimer dispatcherTimer = new(DispatcherPriority.Render);
@@ -65,9 +61,10 @@ public partial class GuildwarsMinimap : UserControl
private Point originPoint = new(0, 0);
private Vector originOffset = new(0, 0);
private Point initialClickPoint = new(0, 0);
private Point lastPathfindingPoint = new(0, 0);
private int pathfindingObjectiveCount = 0;
private DebounceResponse? cachedDebounceResponse;
private PathfindingCache? pathfindingCache;
private SKSurface? entitySurface;
[GenerateDependencyProperty]
private PathingData pathingData = new();
@@ -89,7 +86,7 @@ public partial class GuildwarsMinimap : UserControl
public event EventHandler<LivingEntity>? LivingEntityClicked;
public event EventHandler<PlayerInformation>? PlayerInformationClicked;
public event EventHandler<MapIcon>? MapIconClicked;
public event EventHandler<Profession?> ProfessionClicked;
public event EventHandler<Profession>? ProfessionClicked;
public GuildwarsMinimap()
:this(
@@ -122,6 +119,11 @@ public partial class GuildwarsMinimap : UserControl
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (!this.IsEnabled)
{
return;
}
if (e.Property == ActualHeightProperty ||
e.Property == ActualWidthProperty)
{
@@ -131,6 +133,8 @@ public partial class GuildwarsMinimap : UserControl
{
this.guildwarsEntityDebouncer.ClearCaches();
this.mainPlayerPositionHistory.Clear();
this.lastPathfindingPoint = new Point(0, 0);
this.pathfindingObjectiveCount = 0;
this.UpdateGameData();
this.DrawMap();
}
@@ -147,7 +151,8 @@ public partial class GuildwarsMinimap : UserControl
private void UpdateGameData()
{
if(this.GameData.Valid is false)
if(this.GameData is null ||
this.GameData.Valid is false)
{
return;
}
@@ -162,8 +167,8 @@ public partial class GuildwarsMinimap : UserControl
return;
}
this.TargetEntityId = this.GameData.Session?.CurrentTargetId ?? 0;
this.TargetEntityModelId = (int?)this.GameData.LivingEntities?.FirstOrDefault(e => e.Id == this.TargetEntityId).ModelType ?? 0;
this.TargetEntityId = this.GameData.CurrentTargetId ?? 0;
this.TargetEntityModelId = (int?)this.GameData.LivingEntities?.FirstOrDefault(e => e.Id == this.TargetEntityId)?.ModelType ?? 0;
var screenVirtualWidth = this.ActualWidth / this.Zoom;
var screenVirtualHeight = this.ActualHeight / this.Zoom;
var position = debounceResponse.MainPlayer.Position!.Value;
@@ -203,7 +208,8 @@ public partial class GuildwarsMinimap : UserControl
private void DrawMap()
{
if (this.PathingData.Trapezoids is null)
if (this.PathingData is null ||
this.PathingData.Trapezoids is null)
{
return;
}
@@ -297,6 +303,7 @@ public partial class GuildwarsMinimap : UserControl
this.CalculatePathsToObjectives();
var foregroundColor = this.FindResource("MahApps.Colors.ThemeBackground").Cast<Color>();
bitmap.Clear(Colors.Transparent);
using var context = bitmap.GetBitmapContext();
var sw = Stopwatch.StartNew();
@@ -307,7 +314,8 @@ public partial class GuildwarsMinimap : UserControl
this.originPoint,
this.Zoom,
PositionRadius,
EntitySize);
EntitySize,
foregroundColor);
this.drawingService.DrawEngagementArea(bitmap, this.cachedDebounceResponse ?? new DebounceResponse());
this.drawingService.DrawMainPlayerPositionHistory(bitmap, this.mainPlayerPositionHistory);
this.drawingService.DrawPaths(bitmap, this.pathfindingCache);
@@ -373,16 +381,30 @@ public partial class GuildwarsMinimap : UserControl
return;
}
var playerPosition = new Point
{
X = this.cachedDebounceResponse?.MainPlayer.Position?.X ?? 0,
Y = this.cachedDebounceResponse?.MainPlayer.Position?.Y ?? 0,
};
/*
* If we already calculated paths for the current objectives, return early.
*/
if (currentMapQuests.Count == this.pathfindingObjectiveCount &&
(playerPosition - this.lastPathfindingPoint).Length < PositionRadius + PositionRadius)
{
this.calculatingPathToObjectives = false;
return;
}
this.pathfindingObjectiveCount = currentMapQuests.Count;
this.lastPathfindingPoint = playerPosition;
var pathfindingTasks = currentMapQuests
.Select(questMetaData =>
{
return this.pathfinder.CalculatePath(
this.PathingData,
new Point
{
X = this.cachedDebounceResponse?.MainPlayer.Position?.X ?? 0,
Y = this.cachedDebounceResponse?.MainPlayer.Position?.Y ?? 0,
},
playerPosition,
new Point
{
X = questMetaData.Position!.Value.X,
@@ -392,7 +414,7 @@ public partial class GuildwarsMinimap : UserControl
})
.ToList();
await Task.WhenAll(pathfindingTasks.Append(Task.Delay(MinPathCalculationFrequency)));
await Task.WhenAll(pathfindingTasks);
var paths = new List<PathfindingResponse>();
var colors = new List<Color>();
for(var i = 0; i < currentMapQuests.Count; i++)
@@ -401,7 +423,7 @@ public partial class GuildwarsMinimap : UserControl
var result = await pathfindingTasks[i];
if (result.TryExtractSuccess(out var pathfindingResponse))
{
paths.Add(pathfindingResponse);
paths.Add(pathfindingResponse!);
var questColor = GetQuestColor(quest);
var pathColor = Color.FromArgb(100, questColor.R, questColor.G, questColor.B);
colors.Add(pathColor);
@@ -567,23 +589,26 @@ public partial class GuildwarsMinimap : UserControl
private void GuildwarsMinimap_MouseMove(object sender, MouseEventArgs e)
{
if (this.GameData.Valid is false)
if (this.GameData.Valid is false ||
this.GameData.MainPlayer is null ||
this.GameData.Party is null ||
this.GameData.WorldPlayers is null)
{
return;
}
this.DragMinimap();
if (this.CheckMouseOverEntity(this.GameData.Party!.OfType<IEntity>()) is not null)
if (this.CheckMouseOverEntity(this.GameData.Party.OfType<IEntity>()) is not null)
{
return;
}
if (this.CheckMouseOverEntity(this.GameData.WorldPlayers!.OfType<IEntity>()) is not null)
if (this.CheckMouseOverEntity(this.GameData.WorldPlayers.OfType<IEntity>()) is not null)
{
return;
}
if (this.CheckMouseOverEntity(Enumerable.Repeat(this.GameData.MainPlayer.As<IEntity>(), 1)) is not null)
if (this.CheckMouseOverEntity(Enumerable.Repeat(this.GameData.MainPlayer.Cast<IEntity>(), 1)) is not null)
{
return;
}
@@ -598,14 +623,14 @@ public partial class GuildwarsMinimap : UserControl
return;
}
if (this.CheckMouseOverEntity(this.GameData.MainPlayer!.Value.QuestLog!
if (this.CheckMouseOverEntity(this.GameData.MainPlayer.QuestLog!
.Where(entity => this.drawingService.IsEntityOnScreen(entity.Position, out _, out _))
.OfType<IPositionalEntity>()) is not null)
{
return;
}
if (this.CheckMouseOverEntity(this.GameData.MainPlayer!.Value.QuestLog!
if (this.CheckMouseOverEntity(this.GameData.MainPlayer.QuestLog!
.Where(entity => !this.drawingService.IsEntityOnScreen(entity.Position, out _, out _))
.Select(oldQuestMetadata =>
{
@@ -650,32 +675,32 @@ public partial class GuildwarsMinimap : UserControl
private void QuestContextMenu_QuestContextMenuClicked(object _, QuestMetadata? quest)
{
if (quest is not QuestMetadata)
if (quest is null)
{
return;
}
this.QuestMetadataClicked?.Invoke(this, quest.Value);
this.QuestMetadataClicked?.Invoke(this, quest);
}
private void PlayerContextMenu_PlayerContextMenuClicked(object _, PlayerInformation? playerInformation)
{
if (playerInformation is not PlayerInformation)
if (playerInformation is null)
{
return;
}
this.PlayerInformationClicked?.Invoke(this, playerInformation.Value);
this.PlayerInformationClicked?.Invoke(this, playerInformation);
}
private void LivingEntityContextMenu_LivingEntityContextMenuClicked(object _, LivingEntity? livingEntity)
{
if (livingEntity is not LivingEntity)
if (livingEntity is null)
{
return;
}
this.LivingEntityClicked?.Invoke(this, livingEntity.Value);
this.LivingEntityClicked?.Invoke(this, livingEntity);
}
private void LivingEntityContextMenu_LivingEntityProfessionContextMenuClicked(object _, Profession? e)
@@ -690,12 +715,12 @@ public partial class GuildwarsMinimap : UserControl
private void MapIconContextMenu_MapIconContextMenuClicked(object _, MapIcon? mapIcon)
{
if (mapIcon is not MapIcon)
if (mapIcon is null)
{
return;
}
this.MapIconClicked?.Invoke(this, mapIcon.Value);
this.MapIconClicked?.Invoke(this, mapIcon);
}
private void MaximizeButton_Clicked(object sender, EventArgs e)
@@ -707,21 +732,6 @@ public partial class GuildwarsMinimap : UserControl
}
}
private void VerifySurface()
{
var peekPixels = this.entitySurface?.PeekPixels();
if (!this.resizeEntities &&
this.ActualWidth == peekPixels?.Width &&
this.ActualHeight == peekPixels?.Height)
{
return;
}
var imageInfo = new SKImageInfo((int)this.ActualWidth, (int)this.ActualHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
var surface = SKSurface.Create(imageInfo);
this.entitySurface = surface;
}
private static bool PositionsCollide(Position position1, Position position2)
{
var a = PositionRadius + PositionRadius;
@@ -38,14 +38,18 @@
Background="{DynamicResource Daybreak.Brushes.Background}"
Foreground="{DynamicResource MahApps.Brushes.Accent}"
HorizontalAlignment="Right"
Expired="NotificationView_Expired"/>
MaxHeight="300"
Expired="NotificationView_Expired"
Closed="NotificationView_Closed"/>
</DataTemplate>
<DataTemplate x:Key="ErrorNotificationDataTemplate">
<local:NotificationView
Background="{DynamicResource Daybreak.Brushes.Background}"
Foreground="{DynamicResource MahApps.Brushes.Accent}"
HorizontalAlignment="Right"
Expired="NotificationView_Expired"/>
MaxHeight="300"
Expired="NotificationView_Expired"
Closed="NotificationView_Closed"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
@@ -1,6 +1,8 @@
using Daybreak.Models.Notifications;
using Daybreak.Services.Notifications;
using Daybreak.Services.Sounds;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.ObjectModel;
using System.Core.Extensions;
@@ -17,6 +19,7 @@ public partial class NotificationStackpanel : UserControl
{
private static readonly TimeSpan ProcInterval = TimeSpan.FromSeconds(1);
private readonly ISoundService soundService;
private readonly INotificationProducer notificationProducer;
private readonly DispatcherTimer dispatcherTimer = new();
private CancellationTokenSource? cancellationToken;
@@ -24,13 +27,16 @@ public partial class NotificationStackpanel : UserControl
public ObservableCollection<NotificationWrapper> Notifications { get; } = new();
public NotificationStackpanel() :
this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<INotificationProducer>())
this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<INotificationProducer>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ISoundService>())
{
}
public NotificationStackpanel(
INotificationProducer notificationProducer)
internal NotificationStackpanel(
INotificationProducer notificationProducer,
ISoundService soundService)
{
this.soundService = soundService.ThrowIfNull();
this.notificationProducer = notificationProducer.ThrowIfNull();
this.InitializeComponent();
@@ -58,6 +64,21 @@ public partial class NotificationStackpanel : UserControl
}
this.Notifications.Remove(notificationWrapper);
notificationWrapper.Notification!.Closed = true;
this.soundService.PlayNotifyClose();
}
private void NotificationView_Closed(object sender, EventArgs e)
{
if (sender.As<UserControl>()?.DataContext is not NotificationWrapper notificationWrapper)
{
return;
}
this.Notifications.Remove(notificationWrapper);
this.notificationProducer.OpenNotification(notificationWrapper.Notification!);
notificationWrapper.Notification!.Closed = true;
this.soundService.PlayNotifyClose();
}
private async void ConsumeNotifications()
@@ -66,6 +87,21 @@ public partial class NotificationStackpanel : UserControl
await foreach(var notification in this.notificationProducer.Consume(this.cancellationToken.Token))
{
this.Notifications.Insert(0, new NotificationWrapper { Notification = notification, DispatcherTimer = this.dispatcherTimer });
switch (notification.Level)
{
case LogLevel.Critical:
case LogLevel.Warning:
case LogLevel.Error:
this.soundService.PlayNotifyError();
break;
case LogLevel.Information:
case LogLevel.Debug:
case LogLevel.Trace:
this.soundService.PlayNotifyInformation();
break;
default:
throw new InvalidOperationException($"Unexpected notification level {notification.Level}");
}
}
}
}
@@ -0,0 +1,56 @@
<UserControl x:Class="Daybreak.Controls.Notifications.NotificationTemplate"
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.Notifications"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
xmlns:converters="clr-namespace:Daybreak.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:DateTimeConverter x:Key="DateTimeConverter" Format="f"></converters:DateTimeConverter>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></converters:BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid>
<Border
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="0, 0, 0, 1"/>
<buttons:HighlightButton
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Clicked="HighlightButton_Clicked">
<buttons:HighlightButton.ButtonContent>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="10" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Title, Mode=OneWay}"
FontSize="20"
Grid.Row="0"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding CreationTime, Mode=OneWay, Converter={StaticResource DateTimeConverter}}"
FontSize="12"
Grid.Row="0"
HorizontalAlignment="Right"
Margin="0, 0, 5, 0"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding Description, Mode=OneWay}"
FontSize="14"
Grid.Row="2"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
TextWrapping="Wrap"/>
<Rectangle Grid.RowSpan="3"
Fill="{DynamicResource MahApps.Brushes.Accent}"
Visibility="{Binding Closed, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Opacity="0.3"/>
</Grid>
</buttons:HighlightButton.ButtonContent>
</buttons:HighlightButton>
<buttons:CancelButton Height="20"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Clicked="CancelButton_Clicked"></buttons:CancelButton>
</Grid>
</UserControl>
@@ -0,0 +1,42 @@
using Daybreak.Models.Notifications;
using System;
using System.Windows.Controls;
namespace Daybreak.Controls.Notifications;
/// <summary>
/// Interaction logic for NotificationTemplate.xaml
/// </summary>
public partial class NotificationTemplate : UserControl
{
public event EventHandler<Notification>? OpenClicked;
public event EventHandler<Notification>? RemoveClicked;
public NotificationTemplate()
{
this.InitializeComponent();
}
private void HighlightButton_Clicked(object sender, EventArgs e)
{
if (this.DataContext is not Notification notification)
{
return;
}
this.OpenClicked?.Invoke(this, notification);
// Resetting the data context triggers the data bindings to update
this.DataContext = default;
this.DataContext = notification;
}
private void CancelButton_Clicked(object sender, EventArgs e)
{
if (this.DataContext is not Notification notification)
{
return;
}
this.RemoveClicked?.Invoke(this, notification);
}
}
@@ -13,34 +13,47 @@
MinHeight="50"
Width="300"
d:DesignHeight="450" d:DesignWidth="800">
<buttons:HighlightButton
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid>
<buttons:HighlightButton
HighlightColor="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Background="{Binding ElementName=_this, Path=Background, Mode=OneWay}"
Width="{Binding ElementName=_this, Path=Width, Mode=OneTime}"
Clicked="HighlightButton_Clicked">
<buttons:HighlightButton.ButtonContent>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Border
BorderThickness="1"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
Grid.RowSpan="2"/>
<TextBlock Text="{Binding Notification.Title}"
<buttons:HighlightButton.ButtonContent>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Border
BorderThickness="1"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
Grid.RowSpan="2" />
<TextBlock Text="{Binding Title, Mode=OneWay}"
Grid.Row="0"
FontSize="18"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Center"
Margin="0, 0, 0, 10"/>
<TextBlock Text="{Binding Notification.Description}"
Margin="5, 0, 5, 10" />
<TextBlock Text="{Binding Description, Mode=OneWay}"
Margin="5, 0, 5, 0"
Grid.Row="1"
FontSize="14"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Left"
TextWrapping="Wrap"/>
</Grid>
</buttons:HighlightButton.ButtonContent>
</buttons:HighlightButton>
TextWrapping="Wrap" />
</Grid>
</buttons:HighlightButton.ButtonContent>
</buttons:HighlightButton>
<buttons:CancelButton Height="15"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="5"
Visibility="{Binding ElementName=_this, Path=Dismissible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Clicked="CancelButton_Clicked"></buttons:CancelButton>
</Grid>
</UserControl>
@@ -2,6 +2,7 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Threading;
@@ -16,9 +17,14 @@ public partial class NotificationView : UserControl
private static readonly TimeSpan ExpirationDuration = TimeSpan.FromSeconds(5);
public event EventHandler? Expired;
public event EventHandler? Closed;
[GenerateDependencyProperty]
private bool dismissible;
private DispatcherTimer? dispatcherTimer;
private DateTime notificationExpirationTime = DateTime.Now;
private DateTime showNotificationExpirationTime = DateTime.Now; // The default notification timeout. Can be extended by moving the mouse over the notification
private DateTime requestedNotificationExpirationTime = DateTime.Now; // Some notifications can have their desired timeouts. Don't make the notification disappear before this time
public NotificationView()
{
@@ -33,7 +39,8 @@ public partial class NotificationView : UserControl
return;
}
this.notificationExpirationTime = DateTime.Now + ExpirationDuration;
this.showNotificationExpirationTime = DateTime.Now + ExpirationDuration;
this.requestedNotificationExpirationTime = notificationWrapper.Notification?.ExpirationTime ?? DateTime.Now;
if (this.dispatcherTimer is not null)
{
this.dispatcherTimer.Tick -= this.DispatcherTimer_Tick;
@@ -42,6 +49,7 @@ public partial class NotificationView : UserControl
this.dispatcherTimer = notificationWrapper.DispatcherTimer;
notificationWrapper.DispatcherTimer.Tick += this.DispatcherTimer_Tick;
this.SlideInAnimation();
this.Dismissible = notificationWrapper.Notification?.Dismissible ?? false;
}
private void UserControl_Unloaded(object _, RoutedEventArgs __)
@@ -56,13 +64,18 @@ public partial class NotificationView : UserControl
private void UserControl_PreviewMouseMove(object _, MouseEventArgs __)
{
this.notificationExpirationTime = DateTime.Now + ExpirationDuration;
this.showNotificationExpirationTime = DateTime.Now + ExpirationDuration;
}
private void HighlightButton_Clicked(object sender, EventArgs e)
{
if (this.DataContext is not NotificationWrapper notificationWrapper ||
notificationWrapper.Notification is not INotification notification)
notificationWrapper.Notification is not ICancellableNotification notification)
{
return;
}
if (!notification.Dismissible)
{
return;
}
@@ -72,24 +85,61 @@ public partial class NotificationView : UserControl
this.dispatcherTimer.Tick -= this.DispatcherTimer_Tick;
}
this.SlideOutAnimation();
notification?.OnClick?.Invoke();
this.SlideOutAnimation(this.Closed);
}
private void DispatcherTimer_Tick(object? sender, EventArgs __)
private void CancelButton_Clicked(object sender, EventArgs e)
{
if (DateTime.Now < this.notificationExpirationTime)
if (this.DataContext is not NotificationWrapper notificationWrapper ||
notificationWrapper.Notification is not ICancellableNotification notification)
{
return;
}
if (!notification.Dismissible)
{
return;
}
if (this.dispatcherTimer is not null)
{
this.dispatcherTimer.Tick -= this.DispatcherTimer_Tick;
}
this.SlideOutAnimation(this.Expired);
}
private void DispatcherTimer_Tick(object? sender, EventArgs __)
{
if (sender is not DispatcherTimer senderDispatcherTimer)
{
return;
}
if (this.DataContext is not NotificationWrapper notificationWrapper ||
notificationWrapper.Notification is not ICancellableNotification notification)
{
senderDispatcherTimer.Tick -= this.DispatcherTimer_Tick;
this.SlideOutAnimation(this.Closed);
return;
}
if (notification.CancellationRequested)
{
senderDispatcherTimer.Tick -= this.DispatcherTimer_Tick;
this.SlideOutAnimation(this.Closed);
return;
}
if (DateTime.Now < this.showNotificationExpirationTime ||
DateTime.Now < this.requestedNotificationExpirationTime)
{
return;
}
senderDispatcherTimer.Tick -= this.DispatcherTimer_Tick;
this.SlideOutAnimation();
this.SlideOutAnimation(this.Expired);
}
private void SlideInAnimation()
@@ -106,7 +156,7 @@ public partial class NotificationView : UserControl
this.BeginAnimation(WidthProperty, doubleAnimation);
}
private void SlideOutAnimation()
private void SlideOutAnimation(EventHandler? onCompleted)
{
var doubleAnimation = new DoubleAnimation
{
@@ -119,7 +169,7 @@ public partial class NotificationView : UserControl
doubleAnimation.Completed += (_, e) =>
{
this.Expired?.Invoke(this, e);
onCompleted?.Invoke(this, e);
};
this.BeginAnimation(WidthProperty, doubleAnimation);
}
+1 -1
View File
@@ -18,6 +18,6 @@ public partial class PlayerContextMenu : UserControl
private void TextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
this.PlayerContextMenuClicked?.Invoke(this, this.DataContext as PlayerInformation? ?? default);
this.PlayerContextMenuClicked?.Invoke(this, this.DataContext as PlayerInformation ?? default);
}
}
+1 -1
View File
@@ -19,6 +19,6 @@ public partial class QuestContextMenu : UserControl
private void TextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
this.QuestContextMenuClicked?.Invoke(this, this.DataContext as QuestMetadata? ?? default);
this.QuestContextMenuClicked?.Invoke(this, this.DataContext as QuestMetadata ?? default);
}
}
+2 -1
View File
@@ -11,7 +11,8 @@
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid>
<TextBox Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Foreground, Mode=OneWay}"
<TextBox x:Name="TextBox"
Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Foreground, Mode=OneWay}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontSize, Mode=OneWay}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontFamily, Mode=OneWay}"
FontStretch="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStretch, Mode=OneWay}"
+5
View File
@@ -37,6 +37,11 @@ public partial class SearchTextBox : UserControl
this.dispatcherTimer.Tick += this.DispatcherTimer_Tick;
}
public void FocusOnTextBox()
{
this.TextBox.Focus();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
@@ -0,0 +1,56 @@
<UserControl x:Class="Daybreak.Controls.Templates.BagContentTemplate"
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"
xmlns:converters="clr-namespace:Daybreak.Converters"
xmlns:controls="clr-namespace:Daybreak.Controls"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
DataContextChanged="UserControl_DataContextChanged"
x:Name="_this"
mc:Ignorable="d"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="0, 0, 1, 1"
d:DesignHeight="450" d:DesignWidth="800"
Height="30">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<converters:BooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" TriggerValue="True"></converters:BooleanToVisibilityConverter>
<ContextMenu x:Key="BagItemContextMenu">
<ContextMenu.Template>
<ControlTemplate>
<controls:BagItemContextMenu DataContext="{Binding DataContext, RelativeSource={RelativeSource TemplatedParent}}"
WikiClicked="BagItemContextMenu_WikiClicked"
PriceHistoryClicked="BagItemContextMenu_PriceHistoryClicked"/>
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>
</UserControl.Resources>
<Grid
Visibility="{Binding ElementName=_this, Path=ContentVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
<Image Source="{Binding ElementName=_this, Path=ImageSource, Mode=OneWay}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Height="30"
Width="30"
Visibility="{Binding ElementName=_this, Path=IconVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Cursor="Hand"/>
<TextBlock Text="{Binding ElementName=_this, Path=ItemId, Mode=OneWay}"
FontSize="20"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="{Binding ElementName=_this, Path=IconVisible, Mode=OneWay, Converter={StaticResource InverseBooleanToVisibilityConverter}}"/>
<TextBlock Text="{Binding ElementName=_this, Path=Count, Mode=OneWay}"
FontSize="14"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="5"/>
<buttons:HighlightButton
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
MouseRightButtonDown="HighlightButton_MouseRightButtonDown"
MouseLeftButtonDown="HighlightButton_MouseLeftButtonDown"/>
</Grid>
</UserControl>
@@ -0,0 +1,181 @@
using Daybreak.Launch;
using Daybreak.Models.Guildwars;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Images;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Core.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
using System.Windows.Media;
using static Daybreak.Models.Guildwars.ItemBase;
namespace Daybreak.Controls.Templates;
/// <summary>
/// Interaction logic for BagContentTemplate.xaml
/// </summary>
public partial class BagContentTemplate : UserControl
{
private readonly IImageCache imageCache;
private readonly IIconCache iconCache;
private IBagContent? cachedBagContent;
private string? cachedIconUri = default!;
public event EventHandler<IBagContent>? ItemClicked;
public event EventHandler<ItemBase>? ItemWikiClicked;
public event EventHandler<ItemBase>? PriceHistoryClicked;
[GenerateDependencyProperty]
private ImageSource imageSource = default!;
[GenerateDependencyProperty]
private bool iconVisible;
[GenerateDependencyProperty]
private bool contentVisible;
[GenerateDependencyProperty]
private int itemId;
[GenerateDependencyProperty]
private int count;
public BagContentTemplate()
: this(
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IImageCache>(),
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IIconCache>())
{
}
public BagContentTemplate(
IImageCache imageCache,
IIconCache iconCache)
{
this.imageCache = imageCache.ThrowIfNull();
this.iconCache = iconCache.ThrowIfNull();
this.InitializeComponent();
}
private void HighlightButton_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.DataContext is not BagItem bagItem ||
bagItem.Item is Unknown)
{
return;
}
var itemContextMenu = this.FindResource("BagItemContextMenu");
if (itemContextMenu is not ContextMenu bagItemContextMenu)
{
return;
}
this.ContextMenu = bagItemContextMenu;
this.ContextMenu.DataContext = bagItem;
this.ContextMenu.IsOpen = true;
}
private void HighlightButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.DataContext is not IBagContent bagContent)
{
return;
}
this.ItemClicked?.Invoke(this, bagContent);
}
private void BagItemContextMenu_WikiClicked(object _, ItemBase e)
{
this.ItemWikiClicked?.Invoke(this, e);
this.ContextMenu.IsOpen = false;
}
private void BagItemContextMenu_PriceHistoryClicked(object _, ItemBase e)
{
this.PriceHistoryClicked?.Invoke(this, e);
this.ContextMenu.IsOpen = false;
}
private async void UserControl_DataContextChanged(object _, DependencyPropertyChangedEventArgs __)
{
if (this.DataContext is not IBagContent bagContent)
{
this.cachedBagContent = default;
this.ContentVisible = false;
this.ImageSource = default;
this.cachedIconUri = default;
return;
}
if (this.BagContentAlreadyLoaded(bagContent))
{
this.ContentVisible = true;
return;
}
if (bagContent is not BagItem bagItem)
{
if (bagContent is UnknownBagItem unknownBagItem)
{
this.cachedBagContent = unknownBagItem;
this.ContentVisible = true;
this.IconVisible = false;
this.ItemId = (int)unknownBagItem.ItemId;
this.Count = (int)unknownBagItem.Count;
}
return;
}
this.cachedBagContent = bagItem;
this.ContentVisible = true;
this.ItemId = bagItem.Item.Id;
this.Count = (int)bagItem.Count;
var maybeIconUri = await this.iconCache.GetIconUri(bagItem.Item);
if (maybeIconUri is not string iconUri ||
this.cachedIconUri == iconUri)
{
this.IconVisible = this.cachedIconUri == maybeIconUri;
return;
}
this.IconVisible = true;
this.cachedIconUri = iconUri;
await this.Dispatcher.InvokeAsync(async () =>
{
this.ImageSource = await this.imageCache.GetImage(iconUri).ConfigureAwait(true);
});
}
private bool BagContentAlreadyLoaded(IBagContent bagContent)
{
if (this.cachedBagContent is null)
{
return false;
}
if (bagContent is BagItem newBagItem &&
this.cachedBagContent is BagItem oldBagItem)
{
return newBagItem.Slot == oldBagItem.Slot &&
newBagItem.Count == oldBagItem.Count &&
newBagItem.Item == oldBagItem.Item;
}
if (bagContent is UnknownBagItem newUnknownBagItem &&
this.cachedBagContent is UnknownBagItem oldUnknownBagItem)
{
return newUnknownBagItem.ItemId == oldUnknownBagItem.ItemId &&
newUnknownBagItem.Count == oldUnknownBagItem.Count &&
newUnknownBagItem.Slot == oldUnknownBagItem.Slot;
}
return false;
}
}
@@ -0,0 +1,151 @@
<UserControl x:Class="Daybreak.Controls.Templates.BagItemMenu"
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"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
DataContextChanged="UserControl_DataContextChanged"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Item.Name}"
FontSize="16"
Grid.Row="0"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Center"/>
<WrapPanel HorizontalAlignment="Center"
Grid.Row="1">
<TextBlock Text="Id: "
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<Grid>
<TextBlock Text="{Binding Item.Id}"
FontSize="16"
VerticalAlignment="Center"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding ItemId}"
FontSize="16"
VerticalAlignment="Center"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
</Grid>
</WrapPanel>
<WrapPanel HorizontalAlignment="Center"
Grid.Row="2">
<TextBlock Text="Wiki Url: "
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding Item.WikiUrl}"
FontSize="16"
VerticalAlignment="Center"
Foreground="{DynamicResource MahApps.Brushes.Accent}"
Cursor="Hand"
MouseLeftButtonDown="WikiTextBlock_MouseLeftButtonDown"/>
</WrapPanel>
<WrapPanel HorizontalAlignment="Center"
Grid.Row="3">
<TextBlock Text="Modifiers Hash: "
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBox Text="{Binding ElementName=_this, Path=ModHash, Mode=OneWay}"
FontSize="10"
VerticalAlignment="Center"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
IsReadOnly="True"
BorderThickness="0"
Background="Transparent"/>
</WrapPanel>
<TextBlock Text="Modifiers"
FontSize="16"
Grid.Row="4"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
HorizontalAlignment="Center"/>
<ScrollViewer
Grid.Row="5">
<ItemsControl ItemsSource="{Binding Modifiers, Mode=OneWay}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<WrapPanel
Grid.Row="0">
<TextBlock Text="Identifier: "
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding Identifier}"
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
</WrapPanel>
<WrapPanel
Grid.Row="1">
<TextBlock Text="Argument1: "
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding Argument1}"
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
</WrapPanel>
<WrapPanel
Grid.Row="2">
<TextBlock Text="Argument2: "
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding Argument2}"
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
</WrapPanel>
<WrapPanel
Grid.Row="3">
<TextBlock Text="Packed value: "
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding Modifier}"
FontSize="16"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"/>
</WrapPanel>
<Border BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="0, 0, 0, 1"
Grid.Row="4"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<buttons:HighlightButton
Grid.Row="6"
Title="Close"
FontSize="16"
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
Height="30"
TextAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
Clicked="HighlightButton_Clicked"/>
</Grid>
</UserControl>
@@ -0,0 +1,66 @@
using Daybreak.Models.Guildwars;
using Daybreak.Services.TradeChat;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Core.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Templates;
/// <summary>
/// Interaction logic for BagItemMenu.xaml
/// </summary>
public partial class BagItemMenu : UserControl
{
private readonly IItemHashService itemHashService;
public event EventHandler? CloseClicked;
public event EventHandler<ItemBase>? ItemWikiClicked;
[GenerateDependencyProperty]
private string modHash = string.Empty;
public BagItemMenu()
: this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IItemHashService>())
{
}
private BagItemMenu(
IItemHashService itemHashService)
{
this.itemHashService = itemHashService.ThrowIfNull();
this.InitializeComponent();
}
private void HighlightButton_Clicked(object sender, EventArgs e)
{
this.CloseClicked?.Invoke(this, e);
}
private void UserControl_DataContextChanged(object _, DependencyPropertyChangedEventArgs e)
{
if (this.DataContext is not IBagContent content)
{
return;
}
this.ModHash = this.itemHashService.ComputeHash(content);
}
private void WikiTextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
{
if (this.DataContext is not IBagContent content)
{
return;
}
if (content is not BagItem bagItem)
{
return;
}
this.ItemWikiClicked?.Invoke(this, bagItem.Item);
}
}
@@ -0,0 +1,20 @@
<UserControl x:Class="Daybreak.Controls.Templates.BagTemplate"
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"
DataContextChanged="UserControl_DataContextChanged"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock FontSize="18"
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
Text="{Binding ElementName=_this, Path=BagName, Mode=OneWay}"
Margin="10, 0, 0, 0"/>
<Rectangle Fill="{DynamicResource MahApps.Brushes.ThemeForeground}"
Height="1"/>
<Grid x:Name="BagHolder" />
</StackPanel>
</UserControl>
@@ -0,0 +1,150 @@
using Daybreak.Models.Guildwars;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
namespace Daybreak.Controls.Templates;
/// <summary>
/// Interaction logic for BagTemplate.xaml
/// </summary>
public partial class BagTemplate : UserControl
{
private const int ItemsPerRow = 5;
public event EventHandler<ItemBase>? ItemWikiClicked;
public event EventHandler<ItemBase>? PriceHistoryClicked;
public event EventHandler<IBagContent>? ItemClicked;
[GenerateDependencyProperty]
private string bagName = string.Empty;
public BagTemplate()
{
this.InitializeComponent();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DataContextProperty)
{
if (this.DataContext is not Bag bag ||
bag.Capacity > 100)
{
this.Visibility = Visibility.Hidden;
}
else
{
this.Visibility = Visibility.Visible;
}
}
}
private void UserControl_DataContextChanged(object _, DependencyPropertyChangedEventArgs __)
{
if (this.DataContext is not Bag bag)
{
return;
}
if (!this.IsScreenVisible())
{
return;
}
if (bag.Capacity > 100)
{
return;
}
if (this.BagHolder.Children.Count != bag.Capacity)
{
this.SetupLayout(bag.Capacity);
}
var distinctItems = bag.Items.DistinctBy(i => i.Slot).ToList();
for (var i = 0; i < this.BagHolder.Children.Count; i++)
{
if (this.BagHolder.Children[i] is not BagContentTemplate bagContentTemplate)
{
throw new InvalidOperationException($"Expected {this.BagName} to contain {nameof(BagContentTemplate)} at slot {i}");
}
var maybeItem = distinctItems.FirstOrDefault(item => (int)item.Slot == i);
bagContentTemplate.DataContext = maybeItem ?? null;
}
}
private void BagContentTemplate_ItemWikiClicked(object? _, ItemBase e)
{
this.ItemWikiClicked?.Invoke(this, e);
}
private void BagContentTemplate_PriceHistoryClicked(object? _, ItemBase e)
{
this.PriceHistoryClicked?.Invoke(this, e);
}
private void BagContentTemplate_ItemClicked(object? _, IBagContent e)
{
this.ItemClicked?.Invoke(this, e);
}
private void SetupLayout(int bagItemsCount)
{
this.BagHolder.RowDefinitions.Clear();
this.BagHolder.ColumnDefinitions.Clear();
foreach(BagContentTemplate child in this.BagHolder.Children)
{
child.ItemWikiClicked -= this.BagContentTemplate_ItemWikiClicked;
child.PriceHistoryClicked -= this.BagContentTemplate_PriceHistoryClicked;
child.ItemClicked -= this.BagContentTemplate_ItemClicked;
}
this.BagHolder.Children.Clear();
var rows = Math.Ceiling((double)bagItemsCount / ItemsPerRow);
for (var i = 0; i < ItemsPerRow; i++)
{
this.BagHolder.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
}
for (var i = 0; i < rows; i++)
{
this.BagHolder.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
}
for (var i = 0; i < bagItemsCount; i++)
{
var currentIndex = this.BagHolder.Children.Count;
var bagContentTemplate = new BagContentTemplate();
Grid.SetColumn(bagContentTemplate, currentIndex % ItemsPerRow);
Grid.SetRow(bagContentTemplate, currentIndex / ItemsPerRow);
bagContentTemplate.VerticalAlignment = VerticalAlignment.Stretch;
bagContentTemplate.HorizontalAlignment = HorizontalAlignment.Stretch;
bagContentTemplate.ItemWikiClicked += this.BagContentTemplate_ItemWikiClicked;
bagContentTemplate.PriceHistoryClicked += this.BagContentTemplate_PriceHistoryClicked;
bagContentTemplate.ItemClicked += this.BagContentTemplate_ItemClicked;
this.BagHolder.Children.Add(bagContentTemplate);
}
}
private bool IsScreenVisible()
{
if (!this.IsVisible)
return false;
var container = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (container is null)
{
return false;
}
var bounds = this.TransformToAncestor(container).TransformBounds(new Rect(0, 0, this.RenderSize.Width, this.RenderSize.Height));
var size = new Rect(0, 0, container.ActualWidth, container.ActualHeight);
return size.IntersectsWith(bounds);
}
}
+23 -3
View File
@@ -149,8 +149,22 @@
<TextBlock Grid.Column="7" Grid.Row="1" TextWrapping="Wrap" FontSize="16"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Text="{Binding ElementName=_this, Path=BuildEntry.EigthSkill.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.Column="1" Grid.RowSpan="6"
x:Name="SideHolder">
<local:ChromiumBrowserWrapper x:Name="SkillBrowser"
MaxWidth="0"
ControlsEnabled="True"
AddressBarReadonly="True"
CanNavigate="True"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
CanDownloadBuild="True"
BuildDecoded="SkillBrowser_BuildDecoded"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
PreventDispose="True"
MaximizeClicked="SkillBrowser_MaximizeClicked" />
</Grid>
<Grid Grid.Column="1" Grid.RowSpan="6">
<Grid x:Name="SkillListContainer">
@@ -158,7 +172,8 @@
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<local:SearchTextBox FontSize="20"
<local:SearchTextBox x:Name="SkillListSearchTextBox"
FontSize="20"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
Background="Transparent"
SearchText="{Binding ElementName=_this, Path=SkillSearchText, Mode=TwoWay}"
@@ -212,5 +227,10 @@
</ItemsControl>
</Grid>
</Grid>
<Grid Grid.RowSpan="6"
Grid.ColumnSpan="2"
Visibility="Hidden"
x:Name="FullScreenHolder">
</Grid>
</Grid>
</UserControl>
@@ -5,7 +5,9 @@ using Daybreak.Models;
using Daybreak.Models.Builds;
using Daybreak.Models.Guildwars;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Navigation;
using Daybreak.Utils;
using Daybreak.Views;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
@@ -19,6 +21,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls.Templates;
@@ -32,13 +35,18 @@ public partial class BuildTemplate : UserControl
private const string InfoNamePlaceholder = "[NAME]";
private const string BaseAddress = $"https://wiki.guildwars.com/wiki/{InfoNamePlaceholder}";
private readonly IBuildTemplateManager buildTemplateManager;
private readonly IViewManager viewManager;
private readonly IAttributePointCalculator? attributePointCalculator;
private readonly CancellationTokenSource? cancellationTokenSource = new();
private bool browserMaximized = false;
private bool showingSkillList = false;
private bool replacingSecondaryProfession;
private bool replacingPrimaryProfession;
private IAttributePointCalculator? attributePointCalculator;
private SkillTemplate? selectingSkillTemplate;
private List<Skill>? skillListCache;
private CancellationTokenSource? cancellationTokenSource = new();
[GenerateDependencyProperty]
private string skillSearchText = string.Empty;
@@ -55,14 +63,20 @@ public partial class BuildTemplate : UserControl
public ObservableCollection<Profession> SecondaryProfessions { get; } = new();
public BuildTemplate()
: this(Launcher.Instance.ApplicationServiceProvider.GetService<IAttributePointCalculator>()!)
: this(Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBuildTemplateManager>(),
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IViewManager>(),
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IAttributePointCalculator>())
{
}
public BuildTemplate(
IBuildTemplateManager buildTemplateManager,
IViewManager viewManager,
IAttributePointCalculator attributePointCalculator)
{
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
this.viewManager = viewManager.ThrowIfNull();
this.attributePointCalculator = attributePointCalculator.ThrowIfNull();
this.InitializeComponent();
@@ -72,6 +86,12 @@ public partial class BuildTemplate : UserControl
this.DataContextChanged += this.BuildTemplate_DataContextChanged;
}
public void BrowseToUrl(string url)
{
this.SkillBrowser.Address = url;
this.ShowInfoBrowser();
}
private void BuildTemplate_Unloaded(object sender, RoutedEventArgs e)
{
this.cancellationTokenSource?.Cancel();
@@ -157,24 +177,28 @@ public partial class BuildTemplate : UserControl
private void BrowseToInfo(string infoName)
{
var address = BaseAddress.Replace(InfoNamePlaceholder, infoName.Replace(" ", "_"));
this.SkillBrowser.Address = address;
this.ShowInfoBrowser();
this.BrowseToUrl(address);
}
private void ShowInfoBrowser()
{
if (this.SkillBrowser.BrowserSupported is true)
if (!this.SkillBrowser.BrowserSupported ||
this.browserMaximized)
{
this.HideSkillListView();
this.SkillBrowser.Width = 400;
return;
}
this.HideSkillListView();
this.SkillBrowser.MaxWidth = 400;
this.SkillBrowser.Visibility = Visibility.Visible;
}
private void HideInfoBrowser()
{
if (this.SkillBrowser.BrowserSupported is true)
{
this.SkillBrowser.Width = 0;
this.SkillBrowser.MaxWidth = 0;
this.SkillBrowser.Visibility = Visibility.Hidden;
}
}
@@ -192,6 +216,7 @@ public partial class BuildTemplate : UserControl
}
this.AvailableSkills = this.skillListCache;
this.SkillListSearchTextBox.FocusOnTextBox();
}
private void HideSkillListView()
@@ -403,4 +428,41 @@ public partial class BuildTemplate : UserControl
this.HideSkillListView();
}
private void SkillBrowser_MaximizeClicked(object sender, EventArgs e)
{
this.browserMaximized = !this.browserMaximized;
if (this.browserMaximized)
{
this.SideHolder.Children.Remove(this.SkillBrowser);
this.FullScreenHolder.Children.Add(this.SkillBrowser);
this.FullScreenHolder.Visibility = Visibility.Visible;
this.SkillBrowser.MaxWidth = double.MaxValue;
}
else
{
this.FullScreenHolder.Children.Remove(this.SkillBrowser);
this.SideHolder.Children.Add(this.SkillBrowser);
this.FullScreenHolder.Visibility = Visibility.Hidden;
this.SkillBrowser.MaxWidth = 400;
}
if (e is MouseButtonEventArgs mouseButtonEventArgs)
{
mouseButtonEventArgs.Handled = true;
}
}
private void SkillBrowser_BuildDecoded(object _, DownloadedBuild e)
{
if (e is null)
{
return;
}
var buildEntry = this.buildTemplateManager.CreateBuild();
buildEntry.Build = e.Build;
buildEntry.Name = e.PreferredName ?? buildEntry.Name;
this.viewManager.ShowView<BuildTemplateView>(buildEntry);
}
}
@@ -9,7 +9,6 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Daybreak.Controls;
@@ -59,7 +58,7 @@ public partial class SkillTemplate : UserControl
if (skill != Skill.NoSkill)
{
var maybeUri = await this.iconRetriever.GetIconUri(skill).ConfigureAwait(true);
this.ImageSource = this.imageCache.GetImage(maybeUri);
this.ImageSource = await this.imageCache.GetImage(maybeUri).ConfigureAwait(true);
}
else if (this.ImageSource is not null)
{
@@ -0,0 +1,97 @@
<UserControl x:Class="Daybreak.Controls.Templates.TradeChatMessageTemplate"
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"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
xmlns:converters="clr-namespace:Daybreak.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:DateTimeConverter x:Key="DateTimeConverter"></converters:DateTimeConverter>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Border
BorderBrush="{StaticResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
Grid.RowSpan="4"></Border>
<WrapPanel
Margin="10, 10, 10, 0"
Grid.Row="0">
<TextBlock
Text="Player name: "
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="16"
VerticalAlignment="Center"/>
<TextBox
Text="{Binding Sender, Mode=OneWay}"
Foreground="{StaticResource MahApps.Brushes.Accent}"
FontSize="16"
TextWrapping="Wrap"
IsReadOnly="True"
IsReadOnlyCaretVisible="False"
BorderThickness="0, 0, 0, 1"/>
<buttons:CopyButton Height="20" Width="20"
Clicked="NameCopyButton_Clicked"/>
</WrapPanel>
<WrapPanel
Margin="10, 0, 10, 0"
Grid.Row="1">
<TextBlock
Text="Message: "
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="16"
VerticalAlignment="Center"/>
<TextBox
Text="{Binding Message, Mode=OneWay}"
Foreground="{StaticResource MahApps.Brushes.Accent}"
FontSize="16"
TextWrapping="Wrap"
IsReadOnly="True"
IsReadOnlyCaretVisible="False"
BorderThickness="0, 0, 0, 1"/>
<buttons:CopyButton Height="20" Width="20"
Clicked="MessageCopyButton_Clicked"/>
</WrapPanel>
<WrapPanel
Margin="10, 0, 10, 0"
Grid.Row="2">
<TextBlock
Text="Posted at: "
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="16"
VerticalAlignment="Center"/>
<TextBox
Text="{Binding Timestamp, Mode=OneWay, Converter={StaticResource DateTimeConverter}}"
Foreground="{StaticResource MahApps.Brushes.Accent}"
FontSize="16"
TextWrapping="Wrap"
IsReadOnly="True"
IsReadOnlyCaretVisible="False"
BorderThickness="0, 0, 0, 1"/>
<buttons:CopyButton Height="20" Width="20"
Clicked="TimestampCopyButton_Clicked"/>
</WrapPanel>
<buttons:HighlightButton
Grid.Row="3"
Title="Close"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
Background="{StaticResource Daybreak.Brushes.Background}"
FontSize="16"
HighlightColor="{StaticResource MahApps.Brushes.Accent}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
BorderBrush="{StaticResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
TextPadding="5"
Margin="10"
Clicked="CloseButton_Clicked"></buttons:HighlightButton>
</Grid>
</UserControl>
@@ -0,0 +1,56 @@
using Daybreak.Models.Trade;
using System;
using System.Windows.Controls;
namespace Daybreak.Controls.Templates;
/// <summary>
/// Interaction logic for TradeChatMessageTemplate.xaml
/// </summary>
public partial class TradeChatMessageTemplate : UserControl
{
public event EventHandler<TraderMessage>? NameCopyClicked;
public event EventHandler<TraderMessage>? MessageCopyClicked;
public event EventHandler<TraderMessage>? TimestampCopyClicked;
public event EventHandler? CloseButtonClicked;
public TradeChatMessageTemplate()
{
this.InitializeComponent();
}
private void NameCopyButton_Clicked(object _, EventArgs e)
{
if (this.DataContext is not TraderMessage traderMessage)
{
return;
}
this.NameCopyClicked?.Invoke(this, traderMessage);
}
private void MessageCopyButton_Clicked(object _, EventArgs e)
{
if (this.DataContext is not TraderMessage traderMessage)
{
return;
}
this.MessageCopyClicked?.Invoke(this, traderMessage);
}
private void TimestampCopyButton_Clicked(object _, EventArgs e)
{
if (this.DataContext is not TraderMessage traderMessage)
{
return;
}
this.TimestampCopyClicked?.Invoke(this, traderMessage);
}
private void CloseButton_Clicked(object _, EventArgs e)
{
this.CloseButtonClicked?.Invoke(this, e);
}
}
@@ -24,7 +24,7 @@
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
Height="30">
Height="40">
<WrapPanel
HorizontalAlignment="Right"
VerticalAlignment="Top">
@@ -45,7 +45,8 @@
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
<Border
BorderBrush="{StaticResource MahApps.Brushes.ThemeForeground}"
BorderThickness="0, 0, 0, 1"/>
BorderThickness="0, 0, 0, 1"
Height="40"/>
</Grid>
<Grid Grid.Row="1">
<controls:SearchTextBox
@@ -91,94 +92,18 @@
Fill="{StaticResource Daybreak.Brushes.Background}"
Grid.RowSpan="3"
Visibility="{Binding ElementName=_this, Path=MessageOverlayVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"></Rectangle>
<Grid Grid.RowSpan="3"
<local:TradeChatMessageTemplate
Grid.RowSpan="3"
MaxWidth="600"
MaxHeight="400"
VerticalAlignment="Center"
HorizontalAlignment="Center"
DataContext="{Binding ElementName=_this, Path=SelectedTraderMessage, Mode=OneWay}"
Background="{StaticResource MahApps.Brushes.ThemeBackground}"
Visibility="{Binding ElementName=_this, Path=MessageOverlayVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Border
BorderBrush="{StaticResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
Grid.RowSpan="4"></Border>
<WrapPanel
Margin="10, 10, 10, 0"
Grid.Row="0">
<TextBlock
Text="Player name: "
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="16"
VerticalAlignment="Center"/>
<TextBox
Text="{Binding ElementName=_this, Path=SelectedTraderMessage.Sender, Mode=OneWay}"
Foreground="{StaticResource MahApps.Brushes.Accent}"
FontSize="16"
TextWrapping="Wrap"
IsReadOnly="True"
IsReadOnlyCaretVisible="False"
BorderThickness="0, 0, 0, 1"/>
<buttons:CopyButton Height="20" Width="20"
Clicked="NameCopyButton_Clicked"/>
</WrapPanel>
<WrapPanel
Margin="10, 0, 10, 0"
Grid.Row="1">
<TextBlock
Text="Message: "
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="16"
VerticalAlignment="Center"/>
<TextBox
Text="{Binding ElementName=_this, Path=SelectedTraderMessage.Message, Mode=OneWay}"
Foreground="{StaticResource MahApps.Brushes.Accent}"
FontSize="16"
TextWrapping="Wrap"
IsReadOnly="True"
IsReadOnlyCaretVisible="False"
BorderThickness="0, 0, 0, 1"/>
<buttons:CopyButton Height="20" Width="20"
Clicked="MessageCopyButton_Clicked"/>
</WrapPanel>
<WrapPanel
Margin="10, 0, 10, 0"
Grid.Row="2">
<TextBlock
Text="Posted at: "
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="16"
VerticalAlignment="Center"/>
<TextBox
Text="{Binding ElementName=_this, Path=SelectedTraderMessage.Timestamp, Mode=OneWay, Converter={StaticResource DateTimeConverter}}"
Foreground="{StaticResource MahApps.Brushes.Accent}"
FontSize="16"
TextWrapping="Wrap"
IsReadOnly="True"
IsReadOnlyCaretVisible="False"
BorderThickness="0, 0, 0, 1"/>
<buttons:CopyButton Height="20" Width="20"
Clicked="TimestampCopyButton_Clicked"/>
</WrapPanel>
<buttons:HighlightButton
Grid.Row="3"
Title="Close"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
Background="{StaticResource Daybreak.Brushes.Background}"
FontSize="16"
HighlightColor="{StaticResource MahApps.Brushes.Accent}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
BorderBrush="{StaticResource MahApps.Brushes.ThemeForeground}"
BorderThickness="1"
TextPadding="5"
Margin="10"
Clicked="CloseButton_Clicked"></buttons:HighlightButton>
</Grid>
Visibility="{Binding ElementName=_this, Path=MessageOverlayVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
NameCopyClicked="TradeChatMessageTemplate_NameCopyClicked"
MessageCopyClicked="TradeChatMessageTemplate_MessageCopyClicked"
TimestampCopyClicked="TradeChatMessageTemplate_TimestampCopyClicked"
CloseButtonClicked="TradeChatMessageTemplate_CloseButtonClicked"/>
</Grid>
</UserControl>
@@ -1,8 +1,11 @@
using Daybreak.Models.Trade;
using Daybreak.Launch;
using Daybreak.Models.Trade;
using Daybreak.Services.TradeChat;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions;
using System.Threading;
@@ -11,6 +14,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace Daybreak.Controls.Templates;
@@ -23,6 +27,7 @@ public partial class TradeChatTemplate : UserControl
private readonly TimeSpan timeUpdateInterval = TimeSpan.FromSeconds(1);
private readonly object collectionLock = new();
private readonly DispatcherTimer dispatcherTimer = new();
private readonly IWordHighlightingService wordHighlightingService;
private CancellationTokenSource? cancellationTokenSource;
private DateTime? lastQueryTime;
private DateTime nextQueryTime = DateTime.Now;
@@ -47,7 +52,13 @@ public partial class TradeChatTemplate : UserControl
public ObservableCollection<TraderMessageViewWrapper> TraderMessages { get; } = new();
public TradeChatTemplate()
: this(Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IWordHighlightingService>())
{
}
private TradeChatTemplate(IWordHighlightingService wordHighlightingService)
{
this.wordHighlightingService = wordHighlightingService.ThrowIfNull();
this.InitializeComponent();
}
@@ -67,11 +78,6 @@ public partial class TradeChatTemplate : UserControl
this.SelectedTraderMessage = traderMessage;
}
private void CloseButton_Clicked(object sender, EventArgs e)
{
this.MessageOverlayVisible = false;
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
this.cancellationTokenSource?.Cancel();
@@ -109,35 +115,6 @@ public partial class TradeChatTemplate : UserControl
process.Start();
}
private void NameCopyButton_Clicked(object sender, EventArgs e)
{
if (this.SelectedTraderMessage is null)
{
return;
}
this.NameCopyClicked?.Invoke(this, this.SelectedTraderMessage);
}
private void MessageCopyButton_Clicked(object sender, EventArgs e)
{
if (this.SelectedTraderMessage is null)
{
return;
}
this.MessageCopyClicked?.Invoke(this, this.SelectedTraderMessage);
}
private void TimestampCopyButton_Clicked(object sender, EventArgs e)
{
if (this.SelectedTraderMessage is null)
{
return;
}
this.TimestampCopyClicked?.Invoke(this, this.SelectedTraderMessage);
}
private void InitializeLatestTrades()
{
@@ -165,14 +142,39 @@ public partial class TradeChatTemplate : UserControl
await this.GetLiveData(this.cancellationTokenSource?.Token ?? CancellationToken.None);
}
private void TradeChatMessageTemplate_NameCopyClicked(object _, TraderMessage e)
{
this.NameCopyClicked?.Invoke(this, e);
}
private void TradeChatMessageTemplate_MessageCopyClicked(object _, TraderMessage e)
{
this.MessageCopyClicked?.Invoke(this, e);
}
private void TradeChatMessageTemplate_TimestampCopyClicked(object _, TraderMessage e)
{
this.TimestampCopyClicked?.Invoke(this, e);
}
private void TradeChatMessageTemplate_CloseButtonClicked(object _, EventArgs __)
{
this.MessageOverlayVisible = false;
}
private async Task GetLiveData(CancellationToken cancellationToken)
{
var foregroundColor = this.FindResource("MahApps.Brushes.ThemeForeground").Cast<SolidColorBrush>();
var buyColor = this.FindResource("Daybreak.Brushes.Blue").Cast<SolidColorBrush>();
var sellColor = this.FindResource("Daybreak.Brushes.DeepPurple").Cast<SolidColorBrush>();
var tradeColor = this.FindResource("Daybreak.Brushes.DeepOrange").Cast<SolidColorBrush>();
await foreach(var tradeMessage in this.TradeChatService.GetLiveTraderMessages(cancellationToken))
{
var wrapper = new TraderMessageViewWrapper
{
UpdateTimer = this.dispatcherTimer,
TraderMessage = tradeMessage
TraderMessage = tradeMessage,
ColoredTextElements = this.wordHighlightingService.ParseString(tradeMessage.Message, foregroundColor, buyColor, sellColor, tradeColor)
};
await this.Dispatcher.InvokeAsync(() => this.TraderMessages.Insert(0, wrapper));
@@ -183,6 +185,10 @@ public partial class TradeChatTemplate : UserControl
{
await this.Dispatcher.InvokeAsync(() =>
{
var foregroundColor = this.FindResource("MahApps.Brushes.ThemeForeground").Cast<SolidColorBrush>();
var buyColor = this.FindResource("Daybreak.Brushes.Blue").Cast<SolidColorBrush>();
var sellColor = this.FindResource("Daybreak.Brushes.DeepPurple").Cast<SolidColorBrush>();
var tradeColor = this.FindResource("Daybreak.Brushes.DeepOrange").Cast<SolidColorBrush>();
this.TraderMessages.Clear();
foreach (var traderMessage in traderMessages)
{
@@ -190,8 +196,9 @@ public partial class TradeChatTemplate : UserControl
{
UpdateTimer = this.dispatcherTimer,
TraderMessage = traderMessage,
Initialized = !animate
});
Initialized = !animate,
ColoredTextElements = this.wordHighlightingService.ParseString(traderMessage.Message, foregroundColor, buyColor, sellColor, tradeColor)
}); ;
}
});
}
@@ -36,11 +36,21 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock
Text="{Binding TraderMessage.Message}"
FontSize="16"
TextWrapping="Wrap"
TextAlignment="Left" />
<ItemsControl ItemsSource="{Binding ColoredTextElements, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text, Mode=OneWay}"
Foreground="{Binding Color, Mode=OneWay}"
FontSize="16"
Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
Grid.Column="1"
VerticalAlignment="Bottom"
@@ -46,7 +46,6 @@ public partial class TradeMessageTemplate : UserControl
}
this.UpdateDispatcherTimer(traderMessageViewWrapper.UpdateTimer);
if (traderMessageViewWrapper.Initialized)
{
return;
@@ -53,6 +53,6 @@ public partial class TradeQuoteTemplate : UserControl
}
this.ImageVisible = true;
this.ImageSource = this.imageCache.GetImage(imageUri);
this.ImageSource = await this.imageCache.GetImage(imageUri).ConfigureAwait(true);
}
}
@@ -0,0 +1,43 @@
using System;
using System.Extensions;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Daybreak.Converters;
public sealed class BooleanToGridLengthConverter : IValueConverter
{
private static GridLength Collapsed = new(0);
public GridLength VisibleValue { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return this.GetVisibility(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private object GetVisibility(object value)
{
if (value is not bool)
{
return this.VisibleValue;
}
var objValue = value.Cast<bool>();
if (objValue)
{
return this.VisibleValue;
}
else
{
return Collapsed;
}
}
}
+19
View File
@@ -5,6 +5,10 @@ using System.Windows.Data;
namespace Daybreak.Converters;
public sealed class DateTimeConverter : IValueConverter
{
public string? Format { get; set; }
public bool LongTime { get; set; }
public bool LongDate { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not DateTime dateTime)
@@ -12,6 +16,21 @@ public sealed class DateTimeConverter : IValueConverter
return string.Empty;
}
if (this.Format is not null)
{
return dateTime.ToString(this.Format);
}
if (this.LongTime)
{
return dateTime.ToLongTimeString();
}
if (this.LongDate)
{
return dateTime.ToLongDateString();
}
return dateTime.ToShortDateString();
}
@@ -0,0 +1,35 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace Daybreak.Converters;
public sealed class TimespanToETAConverter : IValueConverter
{
private const char PluralAppend = 's';
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GetETAString(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private static string GetETAString(object obj)
{
if (obj is not TimeSpan timeSpan)
{
return string.Empty;
}
if ((int)timeSpan.TotalDays > 0)
{
return $"{(int)timeSpan.TotalDays} day{((int)timeSpan.TotalDays > 1 ? PluralAppend : string.Empty)} remaining";
}
return $"{(int)timeSpan.Hours:D2}:{(int)timeSpan.Minutes:D2}:{(int)timeSpan.Seconds:D2} remaining";
}
}
+18 -8
View File
@@ -13,13 +13,16 @@
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.8.52</Version>
<Version>0.9.8.102</Version>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\NotifyClose.wav" />
<None Remove="Resources\NotifyError.wav" />
<None Remove="Resources\NotifyInformation.wav" />
<None Remove="Services\Drawing\Resources\Bag.svg" />
<None Remove="Services\Drawing\Resources\CircledStar.svg" />
<None Remove="Services\Drawing\Resources\Dungeon.svg" />
@@ -27,6 +30,7 @@
<None Remove="Services\Drawing\Resources\DungeonKey.svg" />
<None Remove="Services\Drawing\Resources\Flag.svg" />
<None Remove="Services\Drawing\Resources\Person.svg" />
<None Remove="Services\Drawing\Resources\QuestionMark.svg" />
<None Remove="Services\Drawing\Resources\Scroll.svg" />
<None Remove="Services\Drawing\Resources\Skull.svg" />
<None Remove="Services\Drawing\Resources\StairsDown.svg" />
@@ -34,12 +38,16 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\NotifyClose.wav" />
<EmbeddedResource Include="Resources\NotifyError.wav" />
<EmbeddedResource Include="Resources\NotifyInformation.wav" />
<EmbeddedResource Include="Services\Drawing\Resources\Bag.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\CircledStar.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\Dungeon.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\DungeonBoss.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\Flag.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\Person.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\QuestionMark.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\Scroll.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\Skull.svg" />
<EmbeddedResource Include="Services\Drawing\Resources\StairsDown.svg" />
@@ -69,16 +77,18 @@
</Target>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
<PackageReference Include="LiteDB" Version="5.0.16" />
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.52" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-beta.710" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="MahApps.Metro" Version="2.4.10" />
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1774.30" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1938.49" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="NAudio" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
<PackageReference Include="Slim" Version="1.9.2" />
@@ -88,11 +98,11 @@
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.2" />
<PackageReference Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.3.1" />
<PackageReference Include="WpfExtended" Version="0.7.2" />
<PackageReference Include="WpfExtended.SourceGeneration" Version="0.1.1" />
<PackageReference Include="WpfExtended.SourceGeneration" Version="0.1.2" />
<PackageReference Include="WpfScreenHelper" Version="2.1.0" />
<PackageReference Include="WriteableBitmapEx" Version="1.6.8" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'">
<Exec Command="echo.&gt;$(Version).version" />

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