Inizializzazione progetto WPF .NET 8.0

- Aggiunto il file di progetto `Amaltea.csproj` per un'applicazione WPF .NET 8.0 con supporto per `Nullable` e `ImplicitUsings`.
- Creata la soluzione `Amaltea.sln` con configurazioni `Debug` e `Release`.
- Aggiunti `App.xaml` e `App.xaml.cs` per la configurazione globale e il punto di ingresso dell'applicazione.
- Creata la finestra principale `MainWindow.xaml` con navigazione, barra di stato e contenuto dinamico.
- Implementata logica di connessione simulata alle API di Betfair in `MainWindow.xaml.cs`.
- Aggiunti convertitori `BlockTypeToBrushConverter` e `NullToVisibilityConverter`.
- Creati `ViewModel` per blocchi strategici e editor di strategie (`StrategyBlockViewModel`, `StrategyEditorViewModel`).
- Aggiunti `StrategyEditorView.xaml` e `StrategyEditorView.xaml.cs` per l'editor di strategie con supporto drag-and-drop e salvataggio in JSON.
- Definiti colori, pennelli e stili globali per uniformare l'interfaccia.
- Popolato l'editor di strategie con dati demo e connessioni di esempio.
This commit is contained in:
Alberto Balbo
2025-09-23 09:00:10 +02:00
parent 01fccc8571
commit 44cfcbfaa3
14 changed files with 933 additions and 0 deletions

11
Amaltea/Amaltea.csproj Normal file
View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>

25
Amaltea/Amaltea.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36429.23 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amaltea", "Amaltea.csproj", "{11514D9A-D918-4E19-84CA-0D72FFA124B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{11514D9A-D918-4E19-84CA-0D72FFA124B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11514D9A-D918-4E19-84CA-0D72FFA124B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11514D9A-D918-4E19-84CA-0D72FFA124B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11514D9A-D918-4E19-84CA-0D72FFA124B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {02F963B3-CBB5-49DB-9A2B-3182AE54069A}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,29 @@
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using Amaltea.ViewModels;
namespace Amaltea.Converters;
public class BlockTypeToBrushConverter : IValueConverter
{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is StrategyBlockType type)
{
var resourceKey = type switch
{
StrategyBlockType.Trigger => "Brush.Block.Trigger",
StrategyBlockType.Condition => "Brush.Block.Condition",
StrategyBlockType.Action => "Brush.Block.Action",
StrategyBlockType.Risk => "Brush.Block.Risk",
_ => "Brush.Block.Trigger"
};
return App.Current.TryFindResource(resourceKey) as Brush;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException();
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Amaltea.Converters;
public class NullToVisibilityConverter : IValueConverter
{
// If parameter == "Invert" then returns Visible when value is null.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool invert = string.Equals(parameter as string, "Invert", StringComparison.OrdinalIgnoreCase);
bool isNull = value is null;
if (invert)
return isNull ? Visibility.Visible : Visibility.Collapsed;
return isNull ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException();
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Amaltea.ViewModels;
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
{
if (Equals(storage, value)) return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Windows;
using System.Windows.Media;
namespace Amaltea.ViewModels;
public enum StrategyBlockType
{
Trigger,
Condition,
Action,
Risk
}
public class StrategyBlockViewModel : BaseViewModel
{
private double _x;
private double _y;
private string _title = string.Empty;
private string? _subtitle;
private StrategyBlockType _blockType;
private bool _isSelected;
public Guid Id { get; } = Guid.NewGuid();
public double X { get => _x; set => SetProperty(ref _x, value); }
public double Y { get => _y; set => SetProperty(ref _y, value); }
public string Title { get => _title; set => SetProperty(ref _title, value); }
public string? Subtitle { get => _subtitle; set => SetProperty(ref _subtitle, value); }
public StrategyBlockType BlockType { get => _blockType; set { if (SetProperty(ref _blockType, value)) RaisePropertyChanged(nameof(BlockBrushKey)); } }
public bool IsSelected { get => _isSelected; set => SetProperty(ref _isSelected, value); }
public string BlockBrushKey => BlockType switch
{
StrategyBlockType.Trigger => "Brush.Block.Trigger",
StrategyBlockType.Condition => "Brush.Block.Condition",
StrategyBlockType.Action => "Brush.Block.Action",
StrategyBlockType.Risk => "Brush.Block.Risk",
_ => "Brush.Block.Trigger"
};
}

View File

@@ -0,0 +1,151 @@
using System.Collections.ObjectModel;
using System.Text.Json;
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace Amaltea.ViewModels;
public class StrategyEditorViewModel : BaseViewModel
{
private StrategyBlockViewModel? _selectedBlock;
private readonly ObservableCollection<StrategyBlockViewModel> _selectedBlocks = new();
public ObservableCollection<StrategyBlockViewModel> Blocks { get; } = new();
public ObservableCollection<StrategyConnectionViewModel> Connections { get; } = new();
public IEnumerable<StrategyBlockViewModel> SelectedBlocks => _selectedBlocks;
public StrategyBlockViewModel? SelectedBlock
{
get => _selectedBlock;
set
{
if (SetProperty(ref _selectedBlock, value))
{
_selectedBlocks.Clear();
if (value != null)
{
value.IsSelected = true;
_selectedBlocks.Add(value);
}
RaisePropertyChanged(nameof(SelectedBlocks));
}
}
}
public StrategyEditorViewModel()
{
// Seed with demo blocks
var b1 = new StrategyBlockViewModel { Title = "Event Trigger", Subtitle = "In-Play Start", X = 60, Y = 60, BlockType = StrategyBlockType.Trigger };
var b2 = new StrategyBlockViewModel { Title = "Condition: Odds", Subtitle = "Back Odds < 3.0", X = 260, Y = 160, BlockType = StrategyBlockType.Condition };
var b3 = new StrategyBlockViewModel { Title = "Action: Back", Subtitle = "Stake 10 EUR", X = 520, Y = 180, BlockType = StrategyBlockType.Action };
var b4 = new StrategyBlockViewModel { Title = "Risk Mgmt", Subtitle = "Stop Loss 5%", X = 760, Y = 260, BlockType = StrategyBlockType.Risk };
Blocks.Add(b1);
Blocks.Add(b2);
Blocks.Add(b3);
Blocks.Add(b4);
Connections.Add(new StrategyConnectionViewModel(b1.Id, b2.Id));
Connections.Add(new StrategyConnectionViewModel(b2.Id, b3.Id));
Connections.Add(new StrategyConnectionViewModel(b3.Id, b4.Id));
}
public StrategyBlockViewModel AddBlock(StrategyBlockType type, string title, string subtitle, double x, double y)
{
var block = new StrategyBlockViewModel { BlockType = type, Title = title, Subtitle = subtitle, X = x, Y = y };
Blocks.Add(block);
return block;
}
public void AddConnection(StrategyBlockViewModel from, StrategyBlockViewModel to)
{
if (from == to) return;
if (Connections.Any(c => c.From == from.Id && c.To == to.Id)) return;
Connections.Add(new StrategyConnectionViewModel(from.Id, to.Id));
}
public void ClearSelection()
{
foreach (var b in _selectedBlocks) b.IsSelected = false;
_selectedBlocks.Clear();
_selectedBlock = null;
RaisePropertyChanged(nameof(SelectedBlocks));
RaisePropertyChanged(nameof(SelectedBlock));
}
public void ToggleSelection(StrategyBlockViewModel block, bool add)
{
if (!add)
{
ClearSelection();
block.IsSelected = true;
_selectedBlocks.Add(block);
_selectedBlock = block;
}
else
{
if (_selectedBlocks.Contains(block))
{
block.IsSelected = false;
_selectedBlocks.Remove(block);
}
else
{
block.IsSelected = true;
_selectedBlocks.Add(block);
}
_selectedBlock = _selectedBlocks.LastOrDefault();
}
RaisePropertyChanged(nameof(SelectedBlocks));
RaisePropertyChanged(nameof(SelectedBlock));
}
public string Serialize()
{
var dto = new StrategyGraphDto
{
Blocks = Blocks.Select(b => new StrategyBlockDto
{
Id = b.Id,
X = b.X,
Y = b.Y,
Title = b.Title,
Subtitle = b.Subtitle,
BlockType = b.BlockType
}).ToList(),
Connections = Connections.Select(c => new StrategyConnectionDto { From = c.From, To = c.To }).ToList()
};
return JsonSerializer.Serialize(dto, new JsonSerializerOptions { WriteIndented = true });
}
public void SaveToFile(string path)
{
File.WriteAllText(path, Serialize());
}
}
public record StrategyConnectionViewModel(System.Guid From, System.Guid To);
public class StrategyGraphDto
{
public List<StrategyBlockDto> Blocks { get; set; } = new();
public List<StrategyConnectionDto> Connections { get; set; } = new();
}
public class StrategyBlockDto
{
public System.Guid Id { get; set; }
public double X { get; set; }
public double Y { get; set; }
public string? Title { get; set; }
public string? Subtitle { get; set; }
public StrategyBlockType BlockType { get; set; }
}
public class StrategyConnectionDto
{
public System.Guid From { get; set; }
public System.Guid To { get; set; }
}

View File

@@ -0,0 +1,110 @@
<UserControl x:Class="Amaltea.Views.StrategyEditorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Amaltea.ViewModels"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1000">
<UserControl.Resources>
<DataTemplate x:Key="StrategyBlockTemplate" DataType="{x:Type vm:StrategyBlockViewModel}">
<Border CornerRadius="6" Padding="10 6" BorderThickness="1"
Background="#262B31"
BorderBrush="{Binding BlockType, Converter={StaticResource BlockTypeToBrushConverter}}"
Tag="Block" MouseLeftButtonDown="Block_MouseLeftButtonDown" MouseMove="Block_MouseMove" MouseLeftButtonUp="Block_MouseLeftButtonUp">
<StackPanel>
<TextBlock Text="{Binding Title}" FontWeight="Bold"/>
<TextBlock Text="{Binding Subtitle}" FontSize="11" Foreground="{StaticResource Brush.TextSecondary}"/>
</StackPanel>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="170"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="320"/>
</Grid.ColumnDefinitions>
<!-- Workspace Area -->
<Border Grid.Row="0" Grid.Column="0" Margin="0 0 8 8" Background="{StaticResource Brush.PanelBackground}" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="1" Padding="0">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="#1D1F23">
<Canvas x:Name="WorkspaceCanvas" MinWidth="1200" MinHeight="800" Background="#1D1F23" MouseLeftButtonDown="WorkspaceCanvas_MouseLeftButtonDown">
<!-- Connections Layer -->
<ItemsControl ItemsSource="{Binding Connections}" IsHitTestVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:StrategyConnectionViewModel}">
<Path StrokeThickness="2" Stroke="#3A8DFF" Opacity="0.8">
<Path.Data>
<PathGeometry>
<PathFigure x:Name="pf" StartPoint="0,0"/>
</PathGeometry>
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Blocks -->
<ItemsControl ItemsSource="{Binding Blocks}" ItemTemplate="{StaticResource StrategyBlockTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
</ScrollViewer>
</Border>
<!-- Components Library (Right) -->
<Border Grid.Row="0" Grid.Column="1" Margin="0 0 0 8" Background="{StaticResource Brush.PanelBackground}" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="1" Padding="12">
<StackPanel>
<TextBlock Text="Components" FontWeight="Bold" FontSize="16" Margin="0 0 0 12"/>
<Button Content="Add Trigger" Style="{StaticResource BaseButtonStyle}" Margin="0 4 0 4" Click="AddTrigger_Click"/>
<Button Content="Add Condition" Style="{StaticResource BaseButtonStyle}" Margin="0 4 0 4" Click="AddCondition_Click"/>
<Button Content="Add Action" Style="{StaticResource BaseButtonStyle}" Margin="0 4 0 4" Click="AddAction_Click"/>
<Button Content="Add Risk" Style="{StaticResource BaseButtonStyle}" Margin="0 4 0 4" Click="AddRisk_Click"/>
<Separator Margin="0 10 0 10"/>
<Button Content="Save Strategy (JSON)" Style="{StaticResource AccentButtonStyle}" Click="SaveStrategy_Click"/>
</StackPanel>
</Border>
<!-- Properties Panel (Bottom) -->
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Background="{StaticResource Brush.PanelBackground}" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="1" Padding="16">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="320"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Selected Block" FontWeight="Bold" FontSize="16"/>
<TextBlock Text="None" Foreground="{StaticResource Brush.TextSecondary}" FontStyle="Italic" Visibility="{Binding SelectedBlock, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Invert}"/>
<StackPanel Margin="0 8 0 0" Visibility="{Binding SelectedBlock, Converter={StaticResource NullToVisibilityConverter}}">
<TextBlock Text="Title" FontSize="12" Foreground="{StaticResource Brush.TextSecondary}"/>
<TextBox Text="{Binding SelectedBlock.Title, UpdateSourceTrigger=PropertyChanged}" Margin="0 2 0 8"/>
<TextBlock Text="Subtitle" FontSize="12" Foreground="{StaticResource Brush.TextSecondary}"/>
<TextBox Text="{Binding SelectedBlock.Subtitle, UpdateSourceTrigger=PropertyChanged}" Margin="0 2 0 8"/>
</StackPanel>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Text="(Future) Connections / Advanced Settings" FontSize="12" Foreground="{StaticResource Brush.TextSecondary}"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,95 @@
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Amaltea.ViewModels;
using Microsoft.Win32;
namespace Amaltea.Views
{
public partial class StrategyEditorView : UserControl
{
private StrategyEditorViewModel Vm => (StrategyEditorViewModel)DataContext;
private bool _dragging;
private StrategyBlockViewModel? _dragBlock;
private Point _dragStartPos;
private Point _blockStartPos;
private StrategyBlockViewModel? _pendingConnectionFrom;
public StrategyEditorView()
{
InitializeComponent();
DataContext = new StrategyEditorViewModel();
}
private void AddTrigger_Click(object sender, RoutedEventArgs e) => Vm.AddBlock(StrategyBlockType.Trigger, "Event Trigger", "Custom", 80, 80);
private void AddCondition_Click(object sender, RoutedEventArgs e) => Vm.AddBlock(StrategyBlockType.Condition, "Condition", "Edit...", 120, 120);
private void AddAction_Click(object sender, RoutedEventArgs e) => Vm.AddBlock(StrategyBlockType.Action, "Action", "Edit...", 160, 160);
private void AddRisk_Click(object sender, RoutedEventArgs e) => Vm.AddBlock(StrategyBlockType.Risk, "Risk", "Edit...", 200, 200);
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is not Border border || border.DataContext is not StrategyBlockViewModel block) return;
bool ctrl = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
if (_pendingConnectionFrom != null && _pendingConnectionFrom != block && ctrl)
{
Vm.AddConnection(_pendingConnectionFrom, block);
_pendingConnectionFrom = null;
}
else if (ctrl)
{
_pendingConnectionFrom = block;
}
Vm.ToggleSelection(block, ctrl);
_dragging = true;
_dragBlock = block;
_dragStartPos = e.GetPosition(WorkspaceCanvas);
_blockStartPos = new Point(block.X, block.Y);
border.CaptureMouse();
}
private void Block_MouseMove(object sender, MouseEventArgs e)
{
if (!_dragging || _dragBlock == null) return;
var current = e.GetPosition(WorkspaceCanvas);
var delta = current - _dragStartPos;
_dragBlock.X = _blockStartPos.X + delta.X;
_dragBlock.Y = _blockStartPos.Y + delta.Y;
}
private void Block_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (sender is not Border border) return;
_dragging = false;
_dragBlock = null;
border.ReleaseMouseCapture();
}
private void WorkspaceCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.Source == WorkspaceCanvas)
{
Vm.ClearSelection();
_pendingConnectionFrom = null;
}
}
private void SaveStrategy_Click(object sender, RoutedEventArgs e)
{
var dialog = new SaveFileDialog
{
Filter = "Strategy JSON (*.json)|*.json",
FileName = "strategy.json"
};
if (dialog.ShowDialog() == true)
{
Vm.SaveToFile(dialog.FileName);
}
}
}
}

162
Amaltea/App.xaml Normal file
View File

@@ -0,0 +1,162 @@
<Application x:Class="Amaltea.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Amaltea"
xmlns:conv="clr-namespace:Amaltea.Converters"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- Converters -->
<conv:BlockTypeToBrushConverter x:Key="BlockTypeToBrushConverter"/>
<conv:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
<!-- Color & Brush Resources -->
<Color x:Key="Color.Background.Window">#1E1F23</Color>
<Color x:Key="Color.Background.Panel">#24262B</Color>
<Color x:Key="Color.Background.PanelAlt">#2C2F33</Color>
<Color x:Key="Color.Border.Light">#3A3D43</Color>
<Color x:Key="Color.Accent">#2E8CFF</Color>
<Color x:Key="Color.Accent.Hover">#4FA2FF</Color>
<Color x:Key="Color.Text.Primary">#FFFFFF</Color>
<Color x:Key="Color.Text.Secondary">#B0B4BA</Color>
<Color x:Key="Color.Status.Ok">#2ECC71</Color>
<Color x:Key="Color.Status.Warn">#E5C457</Color>
<Color x:Key="Color.Status.Error">#E55252</Color>
<!-- Strategy block category colors -->
<Color x:Key="Color.Block.Trigger">#2475D8</Color>
<Color x:Key="Color.Block.Condition">#8E44AD</Color>
<Color x:Key="Color.Block.Action">#2ECC71</Color>
<Color x:Key="Color.Block.Risk">#E67E22</Color>
<SolidColorBrush x:Key="Brush.WindowBackground" Color="{StaticResource Color.Background.Window}"/>
<SolidColorBrush x:Key="Brush.PanelBackground" Color="{StaticResource Color.Background.Panel}"/>
<SolidColorBrush x:Key="Brush.PanelAltBackground" Color="{StaticResource Color.Background.PanelAlt}"/>
<SolidColorBrush x:Key="Brush.BorderLight" Color="{StaticResource Color.Border.Light}"/>
<SolidColorBrush x:Key="Brush.Accent" Color="{StaticResource Color.Accent}"/>
<SolidColorBrush x:Key="Brush.AccentHover" Color="{StaticResource Color.Accent.Hover}"/>
<SolidColorBrush x:Key="Brush.TextPrimary" Color="{StaticResource Color.Text.Primary}"/>
<SolidColorBrush x:Key="Brush.TextSecondary" Color="{StaticResource Color.Text.Secondary}"/>
<SolidColorBrush x:Key="Brush.Status.Ok" Color="{StaticResource Color.Status.Ok}"/>
<SolidColorBrush x:Key="Brush.Status.Warn" Color="{StaticResource Color.Status.Warn}"/>
<SolidColorBrush x:Key="Brush.Status.Error" Color="{StaticResource Color.Status.Error}"/>
<SolidColorBrush x:Key="Brush.Block.Trigger" Color="{StaticResource Color.Block.Trigger}"/>
<SolidColorBrush x:Key="Brush.Block.Condition" Color="{StaticResource Color.Block.Condition}"/>
<SolidColorBrush x:Key="Brush.Block.Action" Color="{StaticResource Color.Block.Action}"/>
<SolidColorBrush x:Key="Brush.Block.Risk" Color="{StaticResource Color.Block.Risk}"/>
<!-- Global Window Style -->
<Style TargetType="Window">
<Setter Property="Background" Value="{StaticResource Brush.WindowBackground}"/>
<Setter Property="Foreground" Value="{StaticResource Brush.TextPrimary}"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="14"/>
</Style>
<!-- ScrollViewer default (hide horizontal) -->
<Style TargetType="ScrollViewer">
<Setter Property="HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
</Style>
<!-- Base Button Style -->
<Style x:Key="BaseButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{StaticResource Brush.PanelAltBackground}"/>
<Setter Property="Foreground" Value="{StaticResource Brush.TextPrimary}"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.BorderLight}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10 6"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4">
<ContentPresenter Margin="2" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#33373E"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#3E434B"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Cursor" Value="Arrow"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Navigation Button Style -->
<Style x:Key="NavButtonStyle" TargetType="Button" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Margin" Value="0,2,0,2"/>
<Setter Property="Padding" Value="14 10"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
<!-- Accent Button Style (e.g., Connect) -->
<Style x:Key="AccentButtonStyle" TargetType="Button" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Background" Value="{StaticResource Brush.Accent}"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.Accent}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource Brush.AccentHover}"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.AccentHover}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#1F6BBF"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Status Text Style -->
<Style x:Key="StatusTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="{StaticResource Brush.TextSecondary}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- Strategy Block Base Style -->
<Style x:Key="StrategyBlockBaseStyle" TargetType="Border">
<Setter Property="CornerRadius" Value="6"/>
<Setter Property="Padding" Value="12 8"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.BorderLight}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Background" Value="{StaticResource Brush.PanelAltBackground}"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="8" ShadowDepth="0" Opacity="0.25" Color="#000000"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource Brush.Accent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="StrategyBlockTriggerStyle" TargetType="Border" BasedOn="{StaticResource StrategyBlockBaseStyle}">
<Setter Property="Background" Value="#202A36"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.Block.Trigger}"/>
</Style>
<Style x:Key="StrategyBlockConditionStyle" TargetType="Border" BasedOn="{StaticResource StrategyBlockBaseStyle}">
<Setter Property="Background" Value="#252130"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.Block.Condition}"/>
</Style>
<Style x:Key="StrategyBlockActionStyle" TargetType="Border" BasedOn="{StaticResource StrategyBlockBaseStyle}">
<Setter Property="Background" Value="#1F2D24"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.Block.Action}"/>
</Style>
<Style x:Key="StrategyBlockRiskStyle" TargetType="Border" BasedOn="{StaticResource StrategyBlockBaseStyle}">
<Setter Property="Background" Value="#30271F"/>
<Setter Property="BorderBrush" Value="{StaticResource Brush.Block.Risk}"/>
</Style>
</Application.Resources>
</Application>

14
Amaltea/App.xaml.cs Normal file
View File

@@ -0,0 +1,14 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace Amaltea
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

10
Amaltea/AssemblyInfo.cs Normal file
View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

85
Amaltea/MainWindow.xaml Normal file
View File

@@ -0,0 +1,85 @@
<Window x:Class="Amaltea.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Amaltea"
mc:Ignorable="d"
Title="StrategyBet Desktop" Height="800" Width="1200" MinWidth="960" MinHeight="600" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Left Navigation Panel -->
<Border Grid.Column="0" Background="{StaticResource Brush.PanelBackground}" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="0,0,1,0">
<DockPanel>
<!-- Logo / Title -->
<StackPanel DockPanel.Dock="Top" Margin="12 16 12 24">
<StackPanel Orientation="Horizontal" Margin="0 0 0 12" VerticalAlignment="Center">
<Image Source="pack://application:,,,/Resources/logo.png" Width="28" Height="28" Margin="0,0,8,0"/>
<TextBlock Text="StrategyBet" FontSize="18" FontWeight="Bold"/>
</StackPanel>
<Button x:Name="ConnectButton" Content="Connect" Style="{StaticResource AccentButtonStyle}" Click="ConnectButton_Click"/>
</StackPanel>
<!-- Navigation Buttons -->
<ScrollViewer DockPanel.Dock="Top">
<StackPanel x:Name="NavStackPanel" Margin="12 0 12 12">
<Button Style="{StaticResource NavButtonStyle}" Content="Dashboard" Tag="Dashboard" Click="NavButton_Click"/>
<Button Style="{StaticResource NavButtonStyle}" Content="Strategy Editor" Tag="StrategyEditor" Click="NavButton_Click"/>
<Button Style="{StaticResource NavButtonStyle}" Content="Backtesting" Tag="Backtesting" Click="NavButton_Click"/>
<Button Style="{StaticResource NavButtonStyle}" Content="Account" Tag="Account" Click="NavButton_Click"/>
<Button Style="{StaticResource NavButtonStyle}" Content="Settings" Tag="Settings" Click="NavButton_Click"/>
</StackPanel>
</ScrollViewer>
<!-- Status Section at Bottom -->
<Border DockPanel.Dock="Bottom" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="1,1,0,0" Background="{StaticResource Brush.PanelAltBackground}" Padding="12 10">
<StackPanel Orientation="Vertical">
<TextBlock Text="Betfair Connection" FontSize="11" Foreground="{StaticResource Brush.TextSecondary}"/>
<TextBlock x:Name="ConnectionStatusTextBlock" Text="Disconnected" Style="{StaticResource StatusTextStyle}" Margin="0,4,0,0"/>
</StackPanel>
</Border>
</DockPanel>
</Border>
<!-- Main Content Area -->
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="*"/>
<RowDefinition Height="28"/>
</Grid.RowDefinitions>
<!-- Header -->
<Border Grid.Row="0" Background="{StaticResource Brush.PanelBackground}" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="0,0,0,1">
<DockPanel LastChildFill="True" Margin="16 0">
<TextBlock Text="Dashboard" x:Name="HeaderTitleTextBlock" FontSize="20" FontWeight="Bold" VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" VerticalAlignment="Center">
<TextBlock Text="v0.1" Foreground="{StaticResource Brush.TextSecondary}" VerticalAlignment="Center" Margin="16,0,0,0"/>
</StackPanel>
</DockPanel>
</Border>
<!-- Scrollable Content -->
<ScrollViewer Grid.Row="1">
<Border Margin="16" Background="{StaticResource Brush.PanelBackground}" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="1" Padding="24" CornerRadius="6">
<StackPanel x:Name="MainContentArea">
<TextBlock Text="Benvenuto in StrategyBet Desktop" FontSize="22" FontWeight="Bold"/>
<TextBlock Text="Clicca 'Connect' per iniziare la connessione alle API Betfair. In futuro qui verrà mostrata la dashboard con metriche, strategie attive, mercati live e log attività." TextWrapping="Wrap" Foreground="{StaticResource Brush.TextSecondary}" Margin="0,12,0,0"/>
</StackPanel>
</Border>
</ScrollViewer>
<!-- Footer / StatusBar -->
<Border Grid.Row="2" Background="{StaticResource Brush.PanelBackground}" BorderBrush="{StaticResource Brush.BorderLight}" BorderThickness="0,1,0,0" Padding="12 0 16 0">
<DockPanel>
<TextBlock x:Name="GlobalStatusTextBlock" Text="Ready" Style="{StaticResource StatusTextStyle}"/>
<TextBlock Text="StrategyBet Desktop" DockPanel.Dock="Right" Style="{StaticResource StatusTextStyle}"/>
</DockPanel>
</Border>
</Grid>
</Grid>
</Window>

156
Amaltea/MainWindow.xaml.cs Normal file
View File

@@ -0,0 +1,156 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Amaltea.Views;
namespace Amaltea
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private enum ConnectionState
{
Disconnected,
Connecting,
Connected,
Failed
}
private ConnectionState _connectionState = ConnectionState.Disconnected;
private CancellationTokenSource? _connectionCts;
private readonly Random _random = new();
public MainWindow()
{
InitializeComponent();
}
private async void ConnectButton_Click(object sender, RoutedEventArgs e)
{
if (_connectionState == ConnectionState.Connected)
{
SetConnectionState(ConnectionState.Disconnected, "Disconnected");
return;
}
await ConnectToBetfairAsync();
}
private void SetConnectionState(ConnectionState state, string? overrideText = null)
{
_connectionState = state;
switch (state)
{
case ConnectionState.Disconnected:
ConnectButton.Content = "Connect";
ConnectionStatusTextBlock.Text = overrideText ?? "Disconnected";
GlobalStatusTextBlock.Text = "Ready";
break;
case ConnectionState.Connecting:
ConnectButton.Content = "Cancel";
ConnectionStatusTextBlock.Text = overrideText ?? "Connecting...";
GlobalStatusTextBlock.Text = "Connecting to Betfair...";
break;
case ConnectionState.Connected:
ConnectButton.Content = "Disconnect";
ConnectionStatusTextBlock.Text = overrideText ?? "Connected";
GlobalStatusTextBlock.Text = "Connected to Betfair";
break;
case ConnectionState.Failed:
ConnectButton.Content = "Retry";
ConnectionStatusTextBlock.Text = overrideText ?? "Connection Failed";
GlobalStatusTextBlock.Text = "Connection failed";
break;
}
}
/// <summary>
/// Simulates an async connection to the Betfair API.
/// Later this will be replaced with real authentication / session logic.
/// </summary>
private async Task ConnectToBetfairAsync()
{
// If already connecting allow cancel.
if (_connectionState == ConnectionState.Connecting)
{
_connectionCts?.Cancel();
return;
}
_connectionCts = new CancellationTokenSource();
var token = _connectionCts.Token;
try
{
SetConnectionState(ConnectionState.Connecting);
// Simulate variable latency 3-5 seconds.
var delay = TimeSpan.FromSeconds(_random.Next(3, 6));
await Task.Delay(delay, token);
token.ThrowIfCancellationRequested();
// Randomly simulate success or failure (80% success for now)
var success = _random.NextDouble() < 0.8;
if (success)
{
SetConnectionState(ConnectionState.Connected);
}
else
{
SetConnectionState(ConnectionState.Failed);
}
}
catch (OperationCanceledException)
{
SetConnectionState(ConnectionState.Disconnected, "Canceled");
}
catch (Exception ex)
{
SetConnectionState(ConnectionState.Failed, ex.Message);
}
}
private void NavButton_Click(object sender, RoutedEventArgs e)
{
if (sender is not Button btn) return;
var tag = btn.Tag?.ToString() ?? string.Empty;
HeaderTitleTextBlock.Text = tag switch
{
"Dashboard" => "Dashboard",
"StrategyEditor" => "Strategy Editor",
"Backtesting" => "Backtesting",
"Account" => "Account",
"Settings" => "Settings",
_ => "Dashboard"
};
// Replace main content based on tag (placeholder for now)
MainContentArea.Children.Clear();
if (tag == "StrategyEditor")
{
MainContentArea.Children.Add(new StrategyEditorView());
return;
}
MainContentArea.Children.Add(new TextBlock
{
Text = $"Sezione: {HeaderTitleTextBlock.Text}",
FontSize = 22,
FontWeight = FontWeights.Bold,
Margin = new Thickness(0,0,0,12)
});
MainContentArea.Children.Add(new TextBlock
{
Text = "Contenuto placeholder. Qui verrà caricata l'interfaccia specifica della sezione.",
TextWrapping = TextWrapping.Wrap,
Foreground = (System.Windows.Media.Brush)FindResource("Brush.TextSecondary")
});
}
}
}