switched to new way
This commit is contained in:
parent
f22d182c8f
commit
4a65c8e17b
96 changed files with 4991 additions and 10025 deletions
17
src/Poe2Trade.Ui/App.axaml
Normal file
17
src/Poe2Trade.Ui/App.axaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:conv="using:Poe2Trade.Ui.Converters"
|
||||
x:Class="Poe2Trade.Ui.App"
|
||||
RequestedThemeVariant="Dark">
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
<Application.Resources>
|
||||
<conv:LogLevelToBrushConverter x:Key="LogLevelBrush" />
|
||||
<conv:BoolToOccupiedBrushConverter x:Key="OccupiedBrush" />
|
||||
<conv:LinkModeToColorConverter x:Key="ModeBrush" />
|
||||
<conv:StatusDotBrushConverter x:Key="StatusDotBrush" />
|
||||
<conv:ActiveOpacityConverter x:Key="ActiveOpacity" />
|
||||
<conv:CellBorderConverter x:Key="CellBorderConverter" />
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
44
src/Poe2Trade.Ui/App.axaml.cs
Normal file
44
src/Poe2Trade.Ui/App.axaml.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Poe2Trade.Bot;
|
||||
using Poe2Trade.Core;
|
||||
using Poe2Trade.Ui.ViewModels;
|
||||
using Poe2Trade.Ui.Views;
|
||||
|
||||
namespace Poe2Trade.Ui;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var store = new ConfigStore();
|
||||
var config = AppConfig.Load();
|
||||
var bot = new BotOrchestrator(store, config);
|
||||
|
||||
var mainVm = new MainWindowViewModel(bot)
|
||||
{
|
||||
DebugVm = new DebugViewModel(bot),
|
||||
SettingsVm = new SettingsViewModel(bot)
|
||||
};
|
||||
|
||||
var window = new MainWindow { DataContext = mainVm };
|
||||
window.SetConfigStore(store);
|
||||
desktop.MainWindow = window;
|
||||
|
||||
desktop.ShutdownRequested += async (_, _) =>
|
||||
{
|
||||
await bot.DisposeAsync();
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
99
src/Poe2Trade.Ui/Converters/ValueConverters.cs
Normal file
99
src/Poe2Trade.Ui/Converters/ValueConverters.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using Poe2Trade.Core;
|
||||
using Poe2Trade.Ui.ViewModels;
|
||||
|
||||
namespace Poe2Trade.Ui.Converters;
|
||||
|
||||
public class LogLevelToBrushConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.ToString()?.ToUpperInvariant() switch
|
||||
{
|
||||
"INFO" => new SolidColorBrush(Color.Parse("#58a6ff")),
|
||||
"WARN" or "WARNING" => new SolidColorBrush(Color.Parse("#d29922")),
|
||||
"ERROR" => new SolidColorBrush(Color.Parse("#f85149")),
|
||||
"DEBUG" => new SolidColorBrush(Color.Parse("#8b949e")),
|
||||
_ => new SolidColorBrush(Color.Parse("#e6edf3")),
|
||||
};
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class BoolToOccupiedBrushConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
var occupied = value is true;
|
||||
return new SolidColorBrush(occupied ? Color.Parse("#238636") : Color.Parse("#161b22"));
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class LinkModeToColorConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
LinkMode.Live => new SolidColorBrush(Color.Parse("#1f6feb")),
|
||||
LinkMode.Scrap => new SolidColorBrush(Color.Parse("#9e6a03")),
|
||||
_ => new SolidColorBrush(Color.Parse("#30363d")),
|
||||
};
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class StatusDotBrushConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
var state = value?.ToString() ?? "Idle";
|
||||
return state switch
|
||||
{
|
||||
"Idle" => new SolidColorBrush(Color.Parse("#8b949e")),
|
||||
"Paused" => new SolidColorBrush(Color.Parse("#d29922")),
|
||||
_ => new SolidColorBrush(Color.Parse("#3fb950")),
|
||||
};
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class ActiveOpacityConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is true ? 1.0 : 0.5;
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class CellBorderConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is CellState cell)
|
||||
{
|
||||
return new Thickness(
|
||||
cell.BorderLeft ? 2 : 0,
|
||||
cell.BorderTop ? 2 : 0,
|
||||
cell.BorderRight ? 2 : 0,
|
||||
cell.BorderBottom ? 2 : 0);
|
||||
}
|
||||
return new Thickness(0);
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
19
src/Poe2Trade.Ui/Poe2Trade.Ui.csproj
Normal file
19
src/Poe2Trade.Ui/Poe2Trade.Ui.csproj
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
||||
<ProjectReference Include="..\Poe2Trade.Bot\Poe2Trade.Bot.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
19
src/Poe2Trade.Ui/Program.cs
Normal file
19
src/Poe2Trade.Ui/Program.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Avalonia;
|
||||
using Poe2Trade.Core;
|
||||
|
||||
namespace Poe2Trade.Ui;
|
||||
|
||||
class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Logging.Setup();
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace();
|
||||
}
|
||||
201
src/Poe2Trade.Ui/ViewModels/DebugViewModel.cs
Normal file
201
src/Poe2Trade.Ui/ViewModels/DebugViewModel.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Poe2Trade.Bot;
|
||||
using Poe2Trade.Screen;
|
||||
using Serilog;
|
||||
|
||||
namespace Poe2Trade.Ui.ViewModels;
|
||||
|
||||
public partial class DebugViewModel : ObservableObject
|
||||
{
|
||||
private readonly BotOrchestrator _bot;
|
||||
|
||||
[ObservableProperty] private string _findText = "";
|
||||
[ObservableProperty] private string _debugResult = "";
|
||||
[ObservableProperty] private string _selectedGridLayout = "inventory";
|
||||
[ObservableProperty] private decimal? _clickX;
|
||||
[ObservableProperty] private decimal? _clickY;
|
||||
|
||||
public string[] GridLayoutNames { get; } =
|
||||
[
|
||||
"inventory", "stash12", "stash12_folder", "stash24",
|
||||
"stash24_folder", "seller", "shop", "vendor"
|
||||
];
|
||||
|
||||
public DebugViewModel(BotOrchestrator bot)
|
||||
{
|
||||
_bot = bot;
|
||||
}
|
||||
|
||||
private bool EnsureReady()
|
||||
{
|
||||
if (_bot.IsReady) return true;
|
||||
DebugResult = "Bot not started yet. Press Start first.";
|
||||
return false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task TakeScreenshot()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine("debug", $"screenshot-{DateTime.Now:yyyyMMdd-HHmmss}.png");
|
||||
Directory.CreateDirectory("debug");
|
||||
await _bot.Screen.SaveScreenshot(path);
|
||||
DebugResult = $"Screenshot saved: {path}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugResult = $"Screenshot failed: {ex.Message}";
|
||||
Log.Error(ex, "Screenshot failed");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task RunOcr()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
try
|
||||
{
|
||||
var text = await _bot.Screen.ReadFullScreen();
|
||||
DebugResult = string.IsNullOrWhiteSpace(text) ? "(no text detected)" : text;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugResult = $"OCR failed: {ex.Message}";
|
||||
Log.Error(ex, "OCR failed");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GoHideout()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
try
|
||||
{
|
||||
await _bot.Game.FocusGame();
|
||||
await _bot.Game.GoToHideout();
|
||||
DebugResult = "Sent /hideout command";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugResult = $"Go hideout failed: {ex.Message}";
|
||||
Log.Error(ex, "Go hideout failed");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task FindTextOnScreen()
|
||||
{
|
||||
if (!EnsureReady() || string.IsNullOrWhiteSpace(FindText)) return;
|
||||
try
|
||||
{
|
||||
var pos = await _bot.Screen.FindTextOnScreen(FindText, fuzzy: true);
|
||||
DebugResult = pos.HasValue
|
||||
? $"Found '{FindText}' at ({pos.Value.X}, {pos.Value.Y})"
|
||||
: $"Text '{FindText}' not found";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugResult = $"Find text failed: {ex.Message}";
|
||||
Log.Error(ex, "Find text failed");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task FindAndClick()
|
||||
{
|
||||
if (!EnsureReady() || string.IsNullOrWhiteSpace(FindText)) return;
|
||||
try
|
||||
{
|
||||
await _bot.Game.FocusGame();
|
||||
var pos = await _bot.Inventory.FindAndClickNameplate(FindText);
|
||||
DebugResult = pos.HasValue
|
||||
? $"Clicked '{FindText}' at ({pos.Value.X}, {pos.Value.Y})"
|
||||
: $"Text '{FindText}' not found";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugResult = $"Find & click failed: {ex.Message}";
|
||||
Log.Error(ex, "Find & click failed");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClickAt()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
var x = (int)(ClickX ?? 0);
|
||||
var y = (int)(ClickY ?? 0);
|
||||
try
|
||||
{
|
||||
await _bot.Game.FocusGame();
|
||||
await _bot.Game.LeftClickAt(x, y);
|
||||
DebugResult = $"Clicked at ({x}, {y})";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugResult = $"Click failed: {ex.Message}";
|
||||
Log.Error(ex, "Click at failed");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ScanGrid()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
try
|
||||
{
|
||||
var result = await _bot.Screen.Grid.Scan(SelectedGridLayout);
|
||||
DebugResult = $"Grid scan '{SelectedGridLayout}': " +
|
||||
$"{result.Layout.Cols}x{result.Layout.Rows}, " +
|
||||
$"{result.Occupied.Count} occupied, " +
|
||||
$"{result.Items.Count} items";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugResult = $"Grid scan failed: {ex.Message}";
|
||||
Log.Error(ex, "Grid scan failed");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClickAnge()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
try
|
||||
{
|
||||
await _bot.Game.FocusGame();
|
||||
var pos = await _bot.Inventory.FindAndClickNameplate("ANGE");
|
||||
DebugResult = pos.HasValue ? $"Clicked ANGE at ({pos.Value.X}, {pos.Value.Y})" : "ANGE not found";
|
||||
}
|
||||
catch (Exception ex) { DebugResult = $"Failed: {ex.Message}"; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClickStash()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
try
|
||||
{
|
||||
await _bot.Game.FocusGame();
|
||||
var pos = await _bot.Inventory.FindAndClickNameplate("STASH");
|
||||
DebugResult = pos.HasValue ? $"Clicked STASH at ({pos.Value.X}, {pos.Value.Y})" : "STASH not found";
|
||||
}
|
||||
catch (Exception ex) { DebugResult = $"Failed: {ex.Message}"; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClickSalvage()
|
||||
{
|
||||
if (!EnsureReady()) return;
|
||||
try
|
||||
{
|
||||
await _bot.Game.FocusGame();
|
||||
var pos = await _bot.Inventory.FindAndClickNameplate("SALVAGE BENCH");
|
||||
DebugResult = pos.HasValue ? $"Clicked SALVAGE at ({pos.Value.X}, {pos.Value.Y})" : "SALVAGE BENCH not found";
|
||||
}
|
||||
catch (Exception ex) { DebugResult = $"Failed: {ex.Message}"; }
|
||||
}
|
||||
}
|
||||
183
src/Poe2Trade.Ui/ViewModels/MainWindowViewModel.cs
Normal file
183
src/Poe2Trade.Ui/ViewModels/MainWindowViewModel.cs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Poe2Trade.Bot;
|
||||
using Poe2Trade.Core;
|
||||
using Serilog;
|
||||
|
||||
namespace Poe2Trade.Ui.ViewModels;
|
||||
|
||||
public class LogEntry
|
||||
{
|
||||
public string Time { get; init; } = "";
|
||||
public string Level { get; init; } = "";
|
||||
public string Message { get; init; } = "";
|
||||
}
|
||||
|
||||
public partial class CellState : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private bool _isOccupied;
|
||||
[ObservableProperty] private bool _borderTop;
|
||||
[ObservableProperty] private bool _borderBottom;
|
||||
[ObservableProperty] private bool _borderLeft;
|
||||
[ObservableProperty] private bool _borderRight;
|
||||
}
|
||||
|
||||
public partial class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
private readonly BotOrchestrator _bot;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _state = "Idle";
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(PauseButtonText))]
|
||||
private bool _isPaused;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(StartCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(PauseCommand))]
|
||||
private bool _isStarted;
|
||||
|
||||
[ObservableProperty] private string _newUrl = "";
|
||||
[ObservableProperty] private string _newLinkName = "";
|
||||
[ObservableProperty] private LinkMode _newLinkMode = LinkMode.Live;
|
||||
[ObservableProperty] private int _tradesCompleted;
|
||||
[ObservableProperty] private int _tradesFailed;
|
||||
[ObservableProperty] private int _activeLinksCount;
|
||||
|
||||
public static LinkMode[] LinkModes { get; } = [LinkMode.Live, LinkMode.Scrap];
|
||||
|
||||
public MainWindowViewModel(BotOrchestrator bot)
|
||||
{
|
||||
_bot = bot;
|
||||
_isPaused = bot.IsPaused;
|
||||
|
||||
for (var i = 0; i < 60; i++)
|
||||
InventoryCells.Add(new CellState());
|
||||
|
||||
bot.StatusUpdated += () =>
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
State = bot.State;
|
||||
IsPaused = bot.IsPaused;
|
||||
var status = bot.GetStatus();
|
||||
TradesCompleted = status.TradesCompleted;
|
||||
TradesFailed = status.TradesFailed;
|
||||
ActiveLinksCount = status.Links.Count(l => l.Active);
|
||||
OnPropertyChanged(nameof(Links));
|
||||
UpdateInventoryGrid();
|
||||
});
|
||||
};
|
||||
|
||||
bot.LogMessage += (level, message) =>
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Logs.Add(new LogEntry
|
||||
{
|
||||
Time = DateTime.Now.ToString("HH:mm:ss"),
|
||||
Level = level.ToUpperInvariant(),
|
||||
Message = message
|
||||
});
|
||||
if (Logs.Count > 500) Logs.RemoveAt(0);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public string PauseButtonText => IsPaused ? "Resume" : "Pause";
|
||||
public List<TradeLink> Links => _bot.Links.GetLinks();
|
||||
public ObservableCollection<LogEntry> Logs { get; } = [];
|
||||
public ObservableCollection<CellState> InventoryCells { get; } = [];
|
||||
public int InventoryFreeCells => _bot.IsReady ? _bot.Inventory.Tracker.FreeCells : 60;
|
||||
|
||||
// Sub-ViewModels for tabs
|
||||
public DebugViewModel? DebugVm { get; set; }
|
||||
public SettingsViewModel? SettingsVm { get; set; }
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanStart))]
|
||||
private async Task Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _bot.Start(_bot.Config.TradeUrls);
|
||||
IsStarted = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to start bot");
|
||||
Logs.Add(new LogEntry
|
||||
{
|
||||
Time = DateTime.Now.ToString("HH:mm:ss"),
|
||||
Level = "ERROR",
|
||||
Message = $"Start failed: {ex.Message}"
|
||||
});
|
||||
}
|
||||
}
|
||||
private bool CanStart() => !IsStarted;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanPause))]
|
||||
private void Pause()
|
||||
{
|
||||
if (_bot.IsPaused) _bot.Resume();
|
||||
else _bot.Pause();
|
||||
}
|
||||
private bool CanPause() => IsStarted;
|
||||
|
||||
[RelayCommand]
|
||||
private void AddLink()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NewUrl)) return;
|
||||
_bot.AddLink(NewUrl, NewLinkName, NewLinkMode);
|
||||
NewUrl = "";
|
||||
NewLinkName = "";
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveLink(string? id)
|
||||
{
|
||||
if (id != null) _bot.RemoveLink(id);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleLink(string? id)
|
||||
{
|
||||
if (id == null) return;
|
||||
var link = _bot.Links.GetLink(id);
|
||||
if (link != null) _bot.ToggleLink(id, !link.Active);
|
||||
}
|
||||
|
||||
private void UpdateInventoryGrid()
|
||||
{
|
||||
if (!_bot.IsReady) return;
|
||||
var (grid, items, _) = _bot.Inventory.GetInventoryState();
|
||||
|
||||
for (var r = 0; r < 5; r++)
|
||||
for (var c = 0; c < 12; c++)
|
||||
{
|
||||
var cell = InventoryCells[r * 12 + c];
|
||||
cell.IsOccupied = grid[r, c];
|
||||
cell.BorderTop = false;
|
||||
cell.BorderBottom = false;
|
||||
cell.BorderLeft = false;
|
||||
cell.BorderRight = false;
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
for (var r = item.Row; r < item.Row + item.H; r++)
|
||||
for (var c = item.Col; c < item.Col + item.W; c++)
|
||||
{
|
||||
if (r >= 5 || c >= 12) continue;
|
||||
var cell = InventoryCells[r * 12 + c];
|
||||
if (r == item.Row) cell.BorderTop = true;
|
||||
if (r == item.Row + item.H - 1) cell.BorderBottom = true;
|
||||
if (c == item.Col) cell.BorderLeft = true;
|
||||
if (c == item.Col + item.W - 1) cell.BorderRight = true;
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(InventoryFreeCells));
|
||||
}
|
||||
}
|
||||
65
src/Poe2Trade.Ui/ViewModels/SettingsViewModel.cs
Normal file
65
src/Poe2Trade.Ui/ViewModels/SettingsViewModel.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Poe2Trade.Bot;
|
||||
|
||||
namespace Poe2Trade.Ui.ViewModels;
|
||||
|
||||
public partial class SettingsViewModel : ObservableObject
|
||||
{
|
||||
private readonly BotOrchestrator _bot;
|
||||
|
||||
[ObservableProperty] private string _poe2LogPath = "";
|
||||
[ObservableProperty] private string _windowTitle = "";
|
||||
[ObservableProperty] private decimal? _travelTimeoutMs = 15000;
|
||||
[ObservableProperty] private decimal? _stashScanTimeoutMs = 10000;
|
||||
[ObservableProperty] private decimal? _waitForMoreItemsMs = 20000;
|
||||
[ObservableProperty] private decimal? _betweenTradesDelayMs = 5000;
|
||||
[ObservableProperty] private bool _isSaved;
|
||||
|
||||
public SettingsViewModel(BotOrchestrator bot)
|
||||
{
|
||||
_bot = bot;
|
||||
LoadFromConfig();
|
||||
}
|
||||
|
||||
private void LoadFromConfig()
|
||||
{
|
||||
var s = _bot.Store.Settings;
|
||||
Poe2LogPath = s.Poe2LogPath;
|
||||
WindowTitle = s.Poe2WindowTitle;
|
||||
TravelTimeoutMs = s.TravelTimeoutMs;
|
||||
StashScanTimeoutMs = s.StashScanTimeoutMs;
|
||||
WaitForMoreItemsMs = s.WaitForMoreItemsMs;
|
||||
BetweenTradesDelayMs = s.BetweenTradesDelayMs;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SaveSettings()
|
||||
{
|
||||
_bot.Store.UpdateSettings(s =>
|
||||
{
|
||||
s.Poe2LogPath = Poe2LogPath;
|
||||
s.Poe2WindowTitle = WindowTitle;
|
||||
s.TravelTimeoutMs = (int)(TravelTimeoutMs ?? 15000);
|
||||
s.StashScanTimeoutMs = (int)(StashScanTimeoutMs ?? 10000);
|
||||
s.WaitForMoreItemsMs = (int)(WaitForMoreItemsMs ?? 20000);
|
||||
s.BetweenTradesDelayMs = (int)(BetweenTradesDelayMs ?? 5000);
|
||||
});
|
||||
|
||||
_bot.Config.Poe2LogPath = Poe2LogPath;
|
||||
_bot.Config.Poe2WindowTitle = WindowTitle;
|
||||
_bot.Config.TravelTimeoutMs = (int)(TravelTimeoutMs ?? 15000);
|
||||
_bot.Config.StashScanTimeoutMs = (int)(StashScanTimeoutMs ?? 10000);
|
||||
_bot.Config.WaitForMoreItemsMs = (int)(WaitForMoreItemsMs ?? 20000);
|
||||
_bot.Config.BetweenTradesDelayMs = (int)(BetweenTradesDelayMs ?? 5000);
|
||||
|
||||
IsSaved = true;
|
||||
}
|
||||
|
||||
partial void OnPoe2LogPathChanged(string value) => IsSaved = false;
|
||||
partial void OnWindowTitleChanged(string value) => IsSaved = false;
|
||||
partial void OnTravelTimeoutMsChanged(decimal? value) => IsSaved = false;
|
||||
partial void OnStashScanTimeoutMsChanged(decimal? value) => IsSaved = false;
|
||||
partial void OnWaitForMoreItemsMsChanged(decimal? value) => IsSaved = false;
|
||||
partial void OnBetweenTradesDelayMsChanged(decimal? value) => IsSaved = false;
|
||||
}
|
||||
329
src/Poe2Trade.Ui/Views/MainWindow.axaml
Normal file
329
src/Poe2Trade.Ui/Views/MainWindow.axaml
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:Poe2Trade.Ui.ViewModels"
|
||||
x:Class="Poe2Trade.Ui.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="POE2 Trade Bot"
|
||||
Width="960" Height="720"
|
||||
Background="#0d1117">
|
||||
|
||||
<DockPanel Margin="12">
|
||||
|
||||
<!-- STATUS HEADER -->
|
||||
<Border DockPanel.Dock="Top" Padding="12" Margin="0,0,0,8"
|
||||
Background="#161b22" BorderBrush="#30363d"
|
||||
BorderThickness="1" CornerRadius="8">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<!-- Status dot + text -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8"
|
||||
VerticalAlignment="Center">
|
||||
<Ellipse Width="10" Height="10"
|
||||
Fill="{Binding State, Converter={StaticResource StatusDotBrush}}" />
|
||||
<TextBlock Text="{Binding State}" FontWeight="SemiBold"
|
||||
Foreground="#e6edf3" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Stats cards -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="16"
|
||||
HorizontalAlignment="Center">
|
||||
<Border Background="#21262d" CornerRadius="6" Padding="16,8">
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding ActiveLinksCount}"
|
||||
FontSize="20" FontWeight="Bold" Foreground="#58a6ff"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="ACTIVE LINKS" FontSize="10" Foreground="#8b949e"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="#21262d" CornerRadius="6" Padding="16,8">
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding TradesCompleted}"
|
||||
FontSize="20" FontWeight="Bold" Foreground="#3fb950"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="TRADES DONE" FontSize="10" Foreground="#8b949e"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="#21262d" CornerRadius="6" Padding="16,8">
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding TradesFailed}"
|
||||
FontSize="20" FontWeight="Bold" Foreground="#f85149"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="FAILED" FontSize="10" Foreground="#8b949e"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Controls -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Start" Command="{Binding StartCommand}" />
|
||||
<Button Content="{Binding PauseButtonText}" Command="{Binding PauseCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- TABBED CONTENT -->
|
||||
<TabControl>
|
||||
|
||||
<!-- ========== MAIN TAB ========== -->
|
||||
<TabItem Header="Main">
|
||||
<Grid RowDefinitions="Auto,*" Margin="0,8,0,0">
|
||||
|
||||
<!-- Inventory Grid (12x5) -->
|
||||
<Border Grid.Row="0" Background="#161b22" BorderBrush="#30363d"
|
||||
BorderThickness="1" CornerRadius="8" Padding="10" Margin="0,0,0,8">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<TextBlock Text="INVENTORY" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="#8b949e" />
|
||||
<TextBlock Text="{Binding InventoryFreeCells, StringFormat='{}{0}/60 free'}"
|
||||
FontSize="11" Foreground="#8b949e" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding InventoryCells}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="12" Rows="5" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:CellState">
|
||||
<Border Margin="1" CornerRadius="2" Height="22"
|
||||
Background="{Binding IsOccupied, Converter={StaticResource OccupiedBrush}}"
|
||||
BorderBrush="#3fb950" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Links + Logs split -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="350,*">
|
||||
|
||||
<!-- Left: Trade Links -->
|
||||
<Border Grid.Column="0" Background="#161b22" BorderBrush="#30363d"
|
||||
BorderThickness="1" CornerRadius="8" Padding="10" Margin="0,0,8,0">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Top" Text="TRADE LINKS"
|
||||
FontSize="11" FontWeight="SemiBold" Foreground="#8b949e"
|
||||
Margin="0,0,0,8" />
|
||||
|
||||
<!-- Add link form -->
|
||||
<StackPanel DockPanel.Dock="Top" Spacing="6" Margin="0,0,0,8">
|
||||
<TextBox Text="{Binding NewLinkName}" Watermark="Name (optional)" />
|
||||
<TextBox Text="{Binding NewUrl}" Watermark="Paste trade URL..." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<ComboBox ItemsSource="{x:Static vm:MainWindowViewModel.LinkModes}"
|
||||
SelectedItem="{Binding NewLinkMode}" Width="100" />
|
||||
<Button Content="Add" Command="{Binding AddLinkCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Links list -->
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Links}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Margin="0,2" Padding="8" Background="#21262d"
|
||||
CornerRadius="4"
|
||||
Opacity="{Binding Active, Converter={StaticResource ActiveOpacity}}">
|
||||
<DockPanel>
|
||||
<Button DockPanel.Dock="Right" Content="X" FontSize="10"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding $parent[ItemsControl].((vm:MainWindowViewModel)DataContext).RemoveLinkCommand}"
|
||||
CommandParameter="{Binding Id}" />
|
||||
<CheckBox DockPanel.Dock="Left"
|
||||
IsChecked="{Binding Active}"
|
||||
Margin="0,0,8,0" VerticalAlignment="Center" />
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Background="{Binding Mode, Converter={StaticResource ModeBrush}}"
|
||||
CornerRadius="4" Padding="6,2">
|
||||
<TextBlock Text="{Binding Mode}"
|
||||
FontSize="10" FontWeight="Bold"
|
||||
Foreground="White" />
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Name}" FontSize="12"
|
||||
FontWeight="SemiBold" Foreground="#e6edf3" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding Label}" FontSize="10"
|
||||
Foreground="#8b949e" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Right: Logs -->
|
||||
<Border Grid.Column="1" Background="#161b22" BorderBrush="#30363d"
|
||||
BorderThickness="1" CornerRadius="8" Padding="10">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Top" Text="ACTIVITY LOG"
|
||||
FontSize="11" FontWeight="SemiBold" Foreground="#8b949e"
|
||||
Margin="0,0,0,8" />
|
||||
<ListBox ItemsSource="{Binding Logs}" x:Name="LogList"
|
||||
Background="Transparent" BorderThickness="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:LogEntry">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding Time}" Foreground="#484f58"
|
||||
FontSize="11" FontFamily="Consolas" />
|
||||
<TextBlock Text="{Binding Message}" FontSize="11"
|
||||
FontFamily="Consolas" TextWrapping="Wrap"
|
||||
Foreground="{Binding Level, Converter={StaticResource LogLevelBrush}}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- ========== DEBUG TAB ========== -->
|
||||
<TabItem Header="Debug">
|
||||
<ScrollViewer DataContext="{Binding DebugVm}" Margin="0,8,0,0">
|
||||
<StackPanel Spacing="12" Margin="8" x:DataType="vm:DebugViewModel">
|
||||
|
||||
<!-- Row 1: Quick actions -->
|
||||
<Border Background="#161b22" BorderBrush="#30363d" BorderThickness="1"
|
||||
CornerRadius="8" Padding="12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="QUICK ACTIONS" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="#8b949e" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Screenshot" Command="{Binding TakeScreenshotCommand}" />
|
||||
<Button Content="OCR Screen" Command="{Binding RunOcrCommand}" />
|
||||
<Button Content="Go Hideout" Command="{Binding GoHideoutCommand}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="ANGE" Command="{Binding ClickAngeCommand}" />
|
||||
<Button Content="STASH" Command="{Binding ClickStashCommand}" />
|
||||
<Button Content="SALVAGE" Command="{Binding ClickSalvageCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Row 2: Find text -->
|
||||
<Border Background="#161b22" BorderBrush="#30363d" BorderThickness="1"
|
||||
CornerRadius="8" Padding="12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="FIND TEXT" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="#8b949e" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBox Text="{Binding FindText}" Watermark="Text to find..."
|
||||
Width="300" />
|
||||
<Button Content="Find" Command="{Binding FindTextOnScreenCommand}" />
|
||||
<Button Content="Find & Click" Command="{Binding FindAndClickCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Row 3: Grid scan -->
|
||||
<Border Background="#161b22" BorderBrush="#30363d" BorderThickness="1"
|
||||
CornerRadius="8" Padding="12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="GRID SCAN" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="#8b949e" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<ComboBox ItemsSource="{Binding GridLayoutNames}"
|
||||
SelectedItem="{Binding SelectedGridLayout}" Width="160" />
|
||||
<Button Content="Scan" Command="{Binding ScanGridCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Row 4: Click At -->
|
||||
<Border Background="#161b22" BorderBrush="#30363d" BorderThickness="1"
|
||||
CornerRadius="8" Padding="12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="CLICK AT POSITION" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="#8b949e" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<NumericUpDown Value="{Binding ClickX}" Watermark="X"
|
||||
Width="100" Minimum="0" Maximum="2560" />
|
||||
<NumericUpDown Value="{Binding ClickY}" Watermark="Y"
|
||||
Width="100" Minimum="0" Maximum="1440" />
|
||||
<Button Content="Click" Command="{Binding ClickAtCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Debug result output -->
|
||||
<Border Background="#161b22" BorderBrush="#30363d" BorderThickness="1"
|
||||
CornerRadius="8" Padding="12" MinHeight="60">
|
||||
<StackPanel>
|
||||
<TextBlock Text="OUTPUT" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="#8b949e" Margin="0,0,0,6" />
|
||||
<TextBlock Text="{Binding DebugResult}" FontFamily="Consolas"
|
||||
FontSize="11" Foreground="#e6edf3" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- ========== SETTINGS TAB ========== -->
|
||||
<TabItem Header="Settings">
|
||||
<ScrollViewer DataContext="{Binding SettingsVm}" Margin="0,8,0,0">
|
||||
<StackPanel Spacing="12" Margin="8" MaxWidth="600"
|
||||
x:DataType="vm:SettingsViewModel">
|
||||
|
||||
<Border Background="#161b22" BorderBrush="#30363d" BorderThickness="1"
|
||||
CornerRadius="8" Padding="16">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="GENERAL SETTINGS" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="#8b949e" />
|
||||
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="POE2 Client.txt Path" FontSize="11" Foreground="#8b949e" />
|
||||
<TextBox Text="{Binding Poe2LogPath}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Window Title" FontSize="11" Foreground="#8b949e" />
|
||||
<TextBox Text="{Binding WindowTitle}" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto">
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Spacing="4" Margin="0,0,6,8">
|
||||
<TextBlock Text="Travel Timeout (ms)" FontSize="11" Foreground="#8b949e" />
|
||||
<NumericUpDown Value="{Binding TravelTimeoutMs}" Minimum="1000"
|
||||
Maximum="60000" Increment="1000" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Spacing="4" Margin="6,0,0,8">
|
||||
<TextBlock Text="Stash Scan Timeout (ms)" FontSize="11" Foreground="#8b949e" />
|
||||
<NumericUpDown Value="{Binding StashScanTimeoutMs}" Minimum="1000"
|
||||
Maximum="60000" Increment="1000" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="4" Margin="0,0,6,0">
|
||||
<TextBlock Text="Wait for More Items (ms)" FontSize="11" Foreground="#8b949e" />
|
||||
<NumericUpDown Value="{Binding WaitForMoreItemsMs}" Minimum="1000"
|
||||
Maximum="120000" Increment="1000" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Spacing="4" Margin="6,0,0,0">
|
||||
<TextBlock Text="Delay Between Trades (ms)" FontSize="11" Foreground="#8b949e" />
|
||||
<NumericUpDown Value="{Binding BetweenTradesDelayMs}" Minimum="0"
|
||||
Maximum="60000" Increment="1000" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,4,0,0">
|
||||
<Button Content="Save Settings" Command="{Binding SaveSettingsCommand}" />
|
||||
<TextBlock Text="Saved!" Foreground="#3fb950" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsSaved}" FontWeight="SemiBold" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
69
src/Poe2Trade.Ui/Views/MainWindow.axaml.cs
Normal file
69
src/Poe2Trade.Ui/Views/MainWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using System.Collections.Specialized;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Poe2Trade.Core;
|
||||
using Poe2Trade.Ui.ViewModels;
|
||||
|
||||
namespace Poe2Trade.Ui.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private ConfigStore? _store;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void SetConfigStore(ConfigStore store)
|
||||
{
|
||||
_store = store;
|
||||
var s = store.Settings;
|
||||
if (s.WindowWidth.HasValue && s.WindowHeight.HasValue)
|
||||
{
|
||||
Width = s.WindowWidth.Value;
|
||||
Height = s.WindowHeight.Value;
|
||||
}
|
||||
if (s.WindowX.HasValue && s.WindowY.HasValue)
|
||||
{
|
||||
Position = new PixelPoint((int)s.WindowX.Value, (int)s.WindowY.Value);
|
||||
WindowStartupLocation = WindowStartupLocation.Manual;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is MainWindowViewModel vm)
|
||||
{
|
||||
vm.Logs.CollectionChanged += (_, args) =>
|
||||
{
|
||||
if (args.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
var logList = this.FindControl<ListBox>("LogList");
|
||||
if (logList != null && vm.Logs.Count > 0)
|
||||
logList.ScrollIntoView(vm.Logs[^1]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
if (_store != null)
|
||||
{
|
||||
_store.UpdateSettings(s =>
|
||||
{
|
||||
s.WindowX = Position.X;
|
||||
s.WindowY = Position.Y;
|
||||
s.WindowWidth = Width;
|
||||
s.WindowHeight = Height;
|
||||
});
|
||||
}
|
||||
base.OnClosing(e);
|
||||
}
|
||||
}
|
||||
10
src/Poe2Trade.Ui/app.manifest
Normal file
10
src/Poe2Trade.Ui/app.manifest
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="Poe2Trade"/>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
Loading…
Add table
Add a link
Reference in a new issue