Implement migration plan: models, service, MVVM, XAML layout, icons\n\n- Add Models: HistoryItem, VariableItem, FunctionDefinitionItem, IconFont\n- Add Services: ICalculatorService, CalculatorService wrapping interpreter\n- Update MainViewModel: properties and Submit/ToggleFunctions commands\n- Rebuild Views/MainView.axaml with variables, history, input, functions panel\n- Add BoolToGridLengthConverter for panel toggle\n- Wire Material Design Icons font as Avalonia resource in App.axaml/csproj
This commit is contained in:
parent
22f9043e50
commit
7b1912579f
11 changed files with 342 additions and 9 deletions
|
@ -8,6 +8,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
<!-- Link the icon font from the legacy project into this project as an Avalonia resource -->
|
||||
<AvaloniaResource Include="..\src.4.7\materialdesignicons-webfont.ttf">
|
||||
<Link>Assets/Fonts/materialdesignicons-webfont.ttf</Link>
|
||||
</AvaloniaResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -7,4 +7,9 @@
|
|||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<!-- Material Design Icons font family embedded as an Avalonia resource -->
|
||||
<FontFamily x:Key="MDI">avares://AdvancedCalculator/Assets/Fonts#Material Design Icons</FontFamily>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia;
|
||||
|
||||
namespace AdvancedCalculator.Converters;
|
||||
|
||||
public class BoolToGridLengthConverter : IValueConverter
|
||||
{
|
||||
public static readonly BoolToGridLengthConverter Instance = new();
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo? culture)
|
||||
{
|
||||
var isOpen = value is bool b && b;
|
||||
// Return star when true, otherwise zero pixels
|
||||
return isOpen ? new GridLength(1, GridUnitType.Star) : new GridLength(0, GridUnitType.Pixel);
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo? culture)
|
||||
{
|
||||
if (value is GridLength gl)
|
||||
{
|
||||
return gl.Value > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
28
src/AdvancedCalculator/Models/FunctionDefinitionItem.cs
Normal file
28
src/AdvancedCalculator/Models/FunctionDefinitionItem.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace AdvancedCalculator.Models;
|
||||
|
||||
public class FunctionDefinitionItem
|
||||
{
|
||||
public string FunctionName { get; private set; } = string.Empty;
|
||||
public string FunctionDescription { get; private set; } = string.Empty;
|
||||
public string Icon { get; private set; } = string.Empty;
|
||||
public IEnumerable<KeyValuePair<string, string>> FunctionArguments { get; private set; } = new List<KeyValuePair<string, string>>();
|
||||
|
||||
public static IEnumerable<FunctionDefinitionItem> DefinedFunctions
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new FunctionDefinitionItem { FunctionName = "sin", Icon = IconFont.SineWave, FunctionDescription = "Returns the sine value of a given expression.", FunctionArguments = new[] { new KeyValuePair<string, string>("expression", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "cos", Icon = IconFont.CosineWave, FunctionDescription = "Returns the cosine value of a given expression.", FunctionArguments = new[] { new KeyValuePair<string, string>("expression", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "tan", Icon = IconFont.MathTan, FunctionDescription = "Returns the tangent value of a given expression.", FunctionArguments = new[] { new KeyValuePair<string, string>("expression", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "round", Icon = IconFont.RoundedCorner, FunctionDescription = "Rounds an expression to the nearest whole number.", FunctionArguments = new[] { new KeyValuePair<string, string>("expression", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "sqrt", Icon = IconFont.SquareRoot, FunctionDescription = "Returns the square root of a given expression.", FunctionArguments = new[] { new KeyValuePair<string, string>("expression", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "abs", Icon = IconFont.PlusCircle, FunctionDescription = "Returns the absolute value of a given expression.", FunctionArguments = new[] { new KeyValuePair<string, string>("expression", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "exp", Icon = IconFont.AlphaECircle, FunctionDescription = "Returns the constant e to a given power.", FunctionArguments = new[] { new KeyValuePair<string, string>("power", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "log", Icon = IconFont.MathLog, FunctionDescription = "Returns the log of the first expression to the base of the second expression.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "An expression to compute."), new KeyValuePair<string, string>("base", "An expression to compute.") } };
|
||||
yield return new FunctionDefinitionItem { FunctionName = "precision", Icon = IconFont.DecimalIncrease, FunctionDescription = "Returns the value of expression1 to a given precision.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "An expression to compute."), new KeyValuePair<string, string>("precision", "An expression to compute.") } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
src/AdvancedCalculator/Models/HistoryItem.cs
Normal file
8
src/AdvancedCalculator/Models/HistoryItem.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace AdvancedCalculator.Models;
|
||||
|
||||
public class HistoryItem
|
||||
{
|
||||
public string Input { get; set; } = string.Empty;
|
||||
public string Output { get; set; } = string.Empty;
|
||||
}
|
||||
|
19
src/AdvancedCalculator/Models/IconFont.cs
Normal file
19
src/AdvancedCalculator/Models/IconFont.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace AdvancedCalculator.Models;
|
||||
|
||||
// Slimmed icon font mapping for required glyphs only
|
||||
public static class IconFont
|
||||
{
|
||||
public const string ArrowRightDropCircle = "\U000f0059";
|
||||
public const string DecimalIncrease = "\U000f01b5";
|
||||
public const string Function = "\U000f0295";
|
||||
public const string PlusCircle = "\U000f0417";
|
||||
public const string RoundedCorner = "\U000f0607";
|
||||
public const string SquareRoot = "\U000f0784";
|
||||
public const string SineWave = "\U000f095b";
|
||||
public const string Variable = "\U000f0ae7";
|
||||
public const string AlphaECircle = "\U000f0bf8";
|
||||
public const string MathTan = "\U000f0c98";
|
||||
public const string MathLog = "\U000f1085";
|
||||
public const string CosineWave = "\U000f1479";
|
||||
}
|
||||
|
15
src/AdvancedCalculator/Models/VariableItem.cs
Normal file
15
src/AdvancedCalculator/Models/VariableItem.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace AdvancedCalculator.Models;
|
||||
|
||||
public class VariableItem
|
||||
{
|
||||
public string VariableName { get; set; } = string.Empty;
|
||||
public string Icon { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
|
||||
// True when this variable is defined by an equation/expression
|
||||
public bool IsExpression { get; set; }
|
||||
|
||||
// Computed value for an expression variable (pretty-printed)
|
||||
public string? ExpressionComputation { get; set; }
|
||||
}
|
||||
|
67
src/AdvancedCalculator/Services/CalculatorService.cs
Normal file
67
src/AdvancedCalculator/Services/CalculatorService.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedCalculator.Models;
|
||||
|
||||
// Intentionally keep the csmic/CSMic dependency surface in this file only.
|
||||
// This allows easy adaptation if the interpreter API changes.
|
||||
namespace AdvancedCalculator.Services;
|
||||
|
||||
public class CalculatorService : ICalculatorService
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
|
||||
// We attempt to use the legacy namespace to maximize compatibility.
|
||||
// If using the newer CSMic.StandardLibrary with different namespaces,
|
||||
// update the using directives and type names here.
|
||||
private readonly csmic.InputInterpreter _interpreter = new();
|
||||
|
||||
public Task<InterpretResult> InterpretAsync(string input)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_interpreter.Interpret(input);
|
||||
var output = _interpreter.Output ?? string.Empty;
|
||||
|
||||
// Build variables list
|
||||
var variables = new List<VariableItem>();
|
||||
foreach (var kvp in _interpreter.Variables)
|
||||
{
|
||||
var name = kvp.Key;
|
||||
var variable = kvp.Value;
|
||||
var valueString = variable?.Value?.ToString() ?? string.Empty;
|
||||
|
||||
var isExpression = variable?.Type == csmic.VariableType.Equation;
|
||||
var item = new VariableItem
|
||||
{
|
||||
VariableName = name,
|
||||
Value = valueString,
|
||||
IsExpression = isExpression,
|
||||
ExpressionComputation = null,
|
||||
Icon = IconFont.Variable
|
||||
};
|
||||
|
||||
if (isExpression && !string.IsNullOrWhiteSpace(valueString))
|
||||
{
|
||||
// Compute the expression-based variable's current value
|
||||
_interpreter.Interpret(valueString);
|
||||
item.ExpressionComputation = _interpreter.Output ?? string.Empty;
|
||||
item.Icon = IconFont.Function;
|
||||
}
|
||||
|
||||
variables.Add(item);
|
||||
}
|
||||
|
||||
return new InterpretResult
|
||||
{
|
||||
Output = output,
|
||||
Variables = variables
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
17
src/AdvancedCalculator/Services/ICalculatorService.cs
Normal file
17
src/AdvancedCalculator/Services/ICalculatorService.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedCalculator.Models;
|
||||
|
||||
namespace AdvancedCalculator.Services;
|
||||
|
||||
public interface ICalculatorService
|
||||
{
|
||||
Task<InterpretResult> InterpretAsync(string input);
|
||||
}
|
||||
|
||||
public class InterpretResult
|
||||
{
|
||||
public string Output { get; set; } = string.Empty;
|
||||
public IReadOnlyList<VariableItem> Variables { get; set; } = new List<VariableItem>();
|
||||
}
|
||||
|
|
@ -1,6 +1,64 @@
|
|||
namespace AdvancedCalculator.ViewModels;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using AdvancedCalculator.Models;
|
||||
using AdvancedCalculator.Services;
|
||||
|
||||
namespace AdvancedCalculator.ViewModels;
|
||||
|
||||
public partial class MainViewModel : ViewModelBase
|
||||
{
|
||||
public string Greeting => "Welcome to Avalonia!";
|
||||
private readonly ICalculatorService _calculatorService;
|
||||
|
||||
public MainViewModel()
|
||||
: this(new CalculatorService())
|
||||
{
|
||||
}
|
||||
|
||||
public MainViewModel(ICalculatorService calculatorService)
|
||||
{
|
||||
_calculatorService = calculatorService;
|
||||
History = new ObservableCollection<HistoryItem>();
|
||||
Variables = new ObservableCollection<VariableItem>();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private string _inputText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isFunctionsPanelOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedHistoryIndex = -1;
|
||||
|
||||
public ObservableCollection<HistoryItem> History { get; }
|
||||
public ObservableCollection<VariableItem> Variables { get; }
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleFunctions()
|
||||
{
|
||||
IsFunctionsPanelOpen = !IsFunctionsPanelOpen;
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false)]
|
||||
private async Task Submit()
|
||||
{
|
||||
var text = InputText?.Trim();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
var result = await _calculatorService.InterpretAsync(text);
|
||||
|
||||
History.Add(new HistoryItem { Input = text, Output = result.Output });
|
||||
|
||||
Variables.Clear();
|
||||
foreach (var v in result.Variables)
|
||||
{
|
||||
Variables.Add(v);
|
||||
}
|
||||
|
||||
InputText = string.Empty;
|
||||
SelectedHistoryIndex = History.Count - 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,99 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:AdvancedCalculator.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:m="clr-namespace:AdvancedCalculator.Models"
|
||||
xmlns:conv="clr-namespace:AdvancedCalculator.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="600"
|
||||
x:Class="AdvancedCalculator.Views.MainView"
|
||||
x:DataType="vm:MainViewModel">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:MainViewModel />
|
||||
</Design.DataContext>
|
||||
<UserControl.Resources>
|
||||
<conv:BoolToGridLengthConverter x:Key="BoolToGridLengthConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Grid ColumnDefinitions="*,3*">
|
||||
<!-- Left column: Variables + Functions -->
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="{Binding IsFunctionsPanelOpen, Converter={StaticResource BoolToGridLengthConverter}}" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Variables list -->
|
||||
<ListBox Grid.Row="0" ItemsSource="{Binding Variables}" SelectedIndex="-1">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="m:VariableItem">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="4,2">
|
||||
<TextBlock Grid.Column="0" FontFamily="{StaticResource MDI}"
|
||||
FontSize="22" Text="{Binding Icon}" VerticalAlignment="Center" Margin="0,0,8,0" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding VariableName}" FontWeight="Bold" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Column="2" Spacing="2">
|
||||
<TextBlock Text="{Binding Value}" />
|
||||
<TextBlock IsVisible="{Binding IsExpression}" Text="{Binding ExpressionComputation}" FontStyle="Italic" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!-- Function definitions -->
|
||||
<ListBox Grid.Row="1" BorderThickness="0"
|
||||
ItemsSource="{x:Static m:FunctionDefinitionItem.DefinedFunctions}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="m:FunctionDefinitionItem">
|
||||
<StackPanel Spacing="2" Margin="4,6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock FontFamily="{StaticResource MDI}" Text="{Binding Icon}" />
|
||||
<TextBlock Text="{Binding FunctionName}" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding FunctionDescription}" FontStyle="Italic" TextWrapping="Wrap" />
|
||||
<ItemsControl ItemsSource="{Binding FunctionArguments}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Key}" />
|
||||
<TextBlock Text="{Binding Value}" FontStyle="Italic" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
|
||||
<!-- GridSplitter between columns -->
|
||||
<GridSplitter Grid.Column="0" HorizontalAlignment="Right" Width="5" Background="Transparent" />
|
||||
|
||||
<!-- Right column: History + Input -->
|
||||
<Grid Grid.Column="1" RowDefinitions="*,Auto">
|
||||
<!-- History -->
|
||||
<ListBox Grid.Row="0" ItemsSource="{Binding History}" SelectedIndex="{Binding SelectedHistoryIndex}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="m:HistoryItem">
|
||||
<Grid ColumnDefinitions="Auto,*" Margin="4,2">
|
||||
<TextBlock FontFamily="{StaticResource MDI}" Text="{x:Static m:IconFont.ArrowRightDropCircle}"
|
||||
FontSize="22" VerticalAlignment="Center" Margin="0,0,8,0" />
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock Text="{Binding Input}" />
|
||||
<TextBlock Text="{Binding Output}" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!-- Input Row -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,*" Margin="4">
|
||||
<Button Command="{Binding ToggleFunctionsCommand}" Margin="0,0,6,0">
|
||||
<TextBlock FontFamily="{StaticResource MDI}" Text="{x:Static m:IconFont.Function}" />
|
||||
</Button>
|
||||
<TextBox Grid.Column="1" Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<InputElement.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding SubmitCommand}" />
|
||||
</InputElement.KeyBindings>
|
||||
</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue