UI: grouped functions palette with category headers/icons/colors; seed full function list and categories

This commit is contained in:
Codex CLI 2025-08-28 03:21:43 -05:00
commit 0ac4d1558f
4 changed files with 191 additions and 55 deletions

View file

@ -0,0 +1,31 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using AdvancedCalculator.Models;
namespace AdvancedCalculator.Converters;
public class CategoryToBrushConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not FunctionCategory cat) return Brushes.Gray;
// Soft header background colors per category
return cat switch
{
FunctionCategory.Base => new SolidColorBrush(Color.FromRgb(0xE3, 0xF2, 0xFD)), // light blue 50
FunctionCategory.Angle => new SolidColorBrush(Color.FromRgb(0xE8, 0xF5, 0xE9)), // green 50
FunctionCategory.Rounding => new SolidColorBrush(Color.FromRgb(0xFF, 0xF3, 0xE0)), // orange 50
FunctionCategory.Trigonometry => new SolidColorBrush(Color.FromRgb(0xF3, 0xE5, 0xF5)), // purple 50
FunctionCategory.HyperbolicTrig => new SolidColorBrush(Color.FromRgb(0xE0, 0xF7, 0xFA)), // cyan 50
FunctionCategory.NumberTheory => new SolidColorBrush(Color.FromRgb(0xFF, 0xFD, 0xE7)), // yellow 50
FunctionCategory.Random => new SolidColorBrush(Color.FromRgb(0xFC, 0xE4, 0xEC)), // pink 50
_ => Brushes.Gray
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotSupportedException();
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace AdvancedCalculator.Models;
@ -6,23 +7,115 @@ 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 FunctionCategory Category { get; private set; }
public string Signature => $"{FunctionName}({string.Join(", ", FunctionArguments.Select(a => a.Key))})";
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.") } };
// Base
yield return new FunctionDefinitionItem { FunctionName = "abs", Category = FunctionCategory.Base, FunctionDescription = "Absolute value of a number.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "sign", Category = FunctionCategory.Base, FunctionDescription = "Sign of a number: 1 or -1.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "min", Category = FunctionCategory.Base, FunctionDescription = "Minimum of two numbers.", FunctionArguments = new[] { new KeyValuePair<string, string>("first", "number"), new KeyValuePair<string, string>("second", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "max", Category = FunctionCategory.Base, FunctionDescription = "Maximum of two numbers.", FunctionArguments = new[] { new KeyValuePair<string, string>("first", "number"), new KeyValuePair<string, string>("second", "number") } };
// Angle
yield return new FunctionDefinitionItem { FunctionName = "degrees", Category = FunctionCategory.Angle, FunctionDescription = "Converts radians to degrees.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "radians", Category = FunctionCategory.Angle, FunctionDescription = "Converts degrees to radians.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "wrapangle", Category = FunctionCategory.Angle, FunctionDescription = "Wraps angle into [start, end).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number"), new KeyValuePair<string, string>("periodStart", "number"), new KeyValuePair<string, string>("periodEnd", "number") } };
// Rounding
yield return new FunctionDefinitionItem { FunctionName = "floor", Category = FunctionCategory.Rounding, FunctionDescription = "Largest integer ≤ value.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "ceiling", Category = FunctionCategory.Rounding, FunctionDescription = "Smallest integer ≥ value.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "truncate", Category = FunctionCategory.Rounding, FunctionDescription = "Integer part toward zero.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "round", Category = FunctionCategory.Rounding, FunctionDescription = "Round to precision (banker's).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number"), new KeyValuePair<string, string>("precision", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "frac", Category = FunctionCategory.Rounding, FunctionDescription = "Fractional part of a number.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "clamp", Category = FunctionCategory.Rounding, FunctionDescription = "Clamp to [low, high].", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number"), new KeyValuePair<string, string>("low", "number"), new KeyValuePair<string, string>("high", "number") } };
// Trigonometry
yield return new FunctionDefinitionItem { FunctionName = "sin", Category = FunctionCategory.Trigonometry, FunctionDescription = "Sine of angle (radians).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "cos", Category = FunctionCategory.Trigonometry, FunctionDescription = "Cosine of angle (radians).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "tan", Category = FunctionCategory.Trigonometry, FunctionDescription = "Tangent of angle (radians).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "asin", Category = FunctionCategory.Trigonometry, FunctionDescription = "Arcsine (radians).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "acos", Category = FunctionCategory.Trigonometry, FunctionDescription = "Arccosine (radians).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "atan", Category = FunctionCategory.Trigonometry, FunctionDescription = "Arctangent (radians).", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "atan2", Category = FunctionCategory.Trigonometry, FunctionDescription = "Arctan(y/x) (radians).", FunctionArguments = new[] { new KeyValuePair<string, string>("y", "number"), new KeyValuePair<string, string>("x", "number") } };
// Hyperbolic Trig
yield return new FunctionDefinitionItem { FunctionName = "sinh", Category = FunctionCategory.HyperbolicTrig, FunctionDescription = "Hyperbolic sine.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "cosh", Category = FunctionCategory.HyperbolicTrig, FunctionDescription = "Hyperbolic cosine.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "tanh", Category = FunctionCategory.HyperbolicTrig, FunctionDescription = "Hyperbolic tangent.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "asinh", Category = FunctionCategory.HyperbolicTrig, FunctionDescription = "Inverse hyperbolic sine.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "acosh", Category = FunctionCategory.HyperbolicTrig, FunctionDescription = "Inverse hyperbolic cosine.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "atanh", Category = FunctionCategory.HyperbolicTrig, FunctionDescription = "Inverse hyperbolic tangent.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
// Number Theory
yield return new FunctionDefinitionItem { FunctionName = "fac", Category = FunctionCategory.NumberTheory, FunctionDescription = "Factorial; Γ(n+1) for non-integers; 0 outside 0..20.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "ncr", Category = FunctionCategory.NumberTheory, FunctionDescription = "Binomial coefficient 0 ≤ r ≤ n ≤ 20.", FunctionArguments = new[] { new KeyValuePair<string, string>("first", "number"), new KeyValuePair<string, string>("second", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "npr", Category = FunctionCategory.NumberTheory, FunctionDescription = "Permutations 0 ≤ r ≤ n ≤ 20.", FunctionArguments = new[] { new KeyValuePair<string, string>("first", "number"), new KeyValuePair<string, string>("second", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "gcd", Category = FunctionCategory.NumberTheory, FunctionDescription = "GCD of two positive integers.", FunctionArguments = new[] { new KeyValuePair<string, string>("first", "number"), new KeyValuePair<string, string>("second", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "lcm", Category = FunctionCategory.NumberTheory, FunctionDescription = "LCM of two positive integers.", FunctionArguments = new[] { new KeyValuePair<string, string>("first", "number"), new KeyValuePair<string, string>("second", "number") } };
// Random
yield return new FunctionDefinitionItem { FunctionName = "flip", Category = FunctionCategory.Random, FunctionDescription = "Fair coin flip → boolean.", FunctionArguments = Enumerable.Empty<KeyValuePair<string, string>>() };
yield return new FunctionDefinitionItem { FunctionName = "bern", Category = FunctionCategory.Random, FunctionDescription = "Bernoulli trial with probability p.", FunctionArguments = new[] { new KeyValuePair<string, string>("p", "number in [0,1]") } };
yield return new FunctionDefinitionItem { FunctionName = "rand", Category = FunctionCategory.Random, FunctionDescription = "Uniform random in [0,1).", FunctionArguments = Enumerable.Empty<KeyValuePair<string, string>>() };
yield return new FunctionDefinitionItem { FunctionName = "rands", Category = FunctionCategory.Random, FunctionDescription = "Uniform random in [lower, upper).", FunctionArguments = new[] { new KeyValuePair<string, string>("lower", "number"), new KeyValuePair<string, string>("upper", "number") } };
yield return new FunctionDefinitionItem { FunctionName = "randn", Category = FunctionCategory.Random, FunctionDescription = "Standard normal N(0,1).", FunctionArguments = Enumerable.Empty<KeyValuePair<string, string>>() };
yield return new FunctionDefinitionItem { FunctionName = "randns", Category = FunctionCategory.Random, FunctionDescription = "Normal scaled by (upper lower).", FunctionArguments = new[] { new KeyValuePair<string, string>("lower", "number"), new KeyValuePair<string, string>("upper", "number") } };
}
}
public static IEnumerable<FunctionGroup> DefinedFunctionGroups
{
get
{
var groups = new[]
{
new { Category = FunctionCategory.Base, Name = "Base", Icon = IconFont.FunctionVariant },
new { Category = FunctionCategory.Angle, Name = "Angle", Icon = IconFont.Protractor },
new { Category = FunctionCategory.Rounding, Name = "Rounding", Icon = IconFont.Ruler },
new { Category = FunctionCategory.Trigonometry, Name = "Trigonometry", Icon = IconFont.SineWave },
new { Category = FunctionCategory.HyperbolicTrig, Name = "Hyperbolic Trig", Icon = IconFont.Waveform },
new { Category = FunctionCategory.NumberTheory, Name = "Number Theory", Icon = IconFont.Abacus },
new { Category = FunctionCategory.Random, Name = "Random", Icon = IconFont.DiceMultipleOutline },
};
var all = DefinedFunctions.ToList();
foreach (var g in groups)
{
var items = all.Where(f => f.Category == g.Category).OrderBy(f => f.FunctionName).ToList();
if (items.Count == 0) continue;
yield return new FunctionGroup
{
Category = g.Category,
GroupName = g.Name,
Icon = g.Icon,
Functions = items
};
}
}
}
}
public enum FunctionCategory
{
Base,
Angle,
Rounding,
Trigonometry,
HyperbolicTrig,
NumberTheory,
Random
}
public class FunctionGroup
{
public FunctionCategory Category { get; set; }
public string GroupName { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty; // MDI glyph
public IEnumerable<FunctionDefinitionItem> Functions { get; set; } = new List<FunctionDefinitionItem>();
}

View file

@ -6,6 +6,7 @@ public static class IconFont
public const string ArrowRightDropCircle = "\U000f0059";
public const string DecimalIncrease = "\U000f01b5";
public const string Function = "\U000f0295";
public const string FunctionVariant = "\U000f070f";
public const string PlusCircle = "\U000f0417";
public const string RoundedCorner = "\U000f0607";
public const string SquareRoot = "\U000f0784";
@ -15,5 +16,10 @@ public static class IconFont
public const string MathTan = "\U000f0c98";
public const string MathLog = "\U000f1085";
public const string CosineWave = "\U000f1479";
public const string AngleAcute = "\U000f0937";
public const string Protractor = "\U000f0b4f";
public const string Ruler = "\U000f0463";
public const string Waveform = "\U000f147c";
public const string Abacus = "\U000f16e0";
public const string DiceMultipleOutline = "\U000f1156";
}

View file

@ -15,6 +15,7 @@
<conv:IsZeroConverter x:Key="IsZeroConverter" />
<conv:WidthToSplitViewModeConverter x:Key="WidthToSplitViewModeConverter" />
<conv:WidthToPaneOpenConverter x:Key="WidthToPaneOpenConverter" />
<conv:CategoryToBrushConverter x:Key="CategoryToBrushConverter" />
</UserControl.Resources>
<!-- Replace columns with a responsive SplitView -->
<SplitView x:Name="RootSplit"
@ -132,50 +133,55 @@
<!-- Functions flyout anchored to this button -->
<Button Grid.Column="1" Command="{Binding ToggleFunctionsCommand}" Margin="0,0,6,0" MinHeight="40" MinWidth="40"
AutomationProperties.Name="Toggle functions panel">
<Button.Flyout>
<Flyout IsOpen="{Binding IsFunctionsPanelOpen, Mode=TwoWay}" Placement="BottomEdgeAlignedLeft">
<Border Padding="8" MinWidth="320" MaxHeight="400">
<ScrollViewer>
<ListBox ItemsSource="{x:Static m:FunctionDefinitionItem.DefinedFunctions}"
BorderThickness="0"
AutomationProperties.Name="Functions list">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="m:FunctionDefinitionItem">
<Button x:DataType="vm:MainViewModel"
DataContext="{Binding #Root.DataContext}"
Command="{Binding InsertFunctionCommand}"
CommandParameter="{Binding $parent[ListBoxItem].DataContext}"
Background="Transparent" BorderThickness="0" Padding="8" MinHeight="44"
AutomationProperties.Name="Insert function">
<StackPanel Spacing="8"
DataContext="{Binding $parent[ListBoxItem].DataContext}"
x:DataType="m:FunctionDefinitionItem">
<StackPanel Orientation="Horizontal" Spacing="8">
<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>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Border>
</Flyout>
</Button.Flyout>
<TextBlock FontFamily="{StaticResource MDI}" Text="{x:Static m:IconFont.Function}" />
</Button>
<Button.Flyout>
<Flyout IsOpen="{Binding IsFunctionsPanelOpen, Mode=TwoWay}" Placement="BottomEdgeAlignedLeft">
<Border Padding="8" MinWidth="360" MaxHeight="420">
<ScrollViewer>
<StackPanel>
<ItemsControl ItemsSource="{x:Static m:FunctionDefinitionItem.DefinedFunctionGroups}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="m:FunctionGroup">
<StackPanel Margin="0,0,0,8">
<!-- Group Header -->
<Border Background="{Binding Category, Converter={StaticResource CategoryToBrushConverter}}"
CornerRadius="6" Padding="8" Margin="0,0,0,6">
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
<TextBlock FontFamily="{StaticResource MDI}" Text="{Binding Icon}"/>
<TextBlock Text="{Binding GroupName}" FontWeight="Bold"/>
</StackPanel>
</Border>
<!-- Group Items: simple rows -->
<ListBox ItemsSource="{Binding Functions}" BorderThickness="0" SelectedIndex="-1">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="m:FunctionDefinitionItem">
<Button x:DataType="vm:MainViewModel"
DataContext="{Binding #Root.DataContext}"
Command="{Binding InsertFunctionCommand}"
CommandParameter="{Binding $parent[ListBoxItem].DataContext}"
Background="Transparent" BorderThickness="0" Padding="8" MinHeight="36"
AutomationProperties.Name="Insert function">
<Grid ColumnDefinitions="*,Auto">
<StackPanel>
<TextBlock Text="{Binding Signature}" FontWeight="Bold"/>
<TextBlock Text="{Binding FunctionDescription}" Opacity="0.8"/>
</StackPanel>
</Grid>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Border>
</Flyout>
</Button.Flyout>
<TextBlock FontFamily="{StaticResource MDI}" Text="{x:Static m:IconFont.Function}" />
</Button>
<TextBox Grid.Column="2" Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinHeight="44"
AutomationProperties.Name="Expression input"