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.Collections.Generic;
using System.Linq;
namespace AdvancedCalculator.Models; namespace AdvancedCalculator.Models;
@ -6,23 +7,115 @@ public class FunctionDefinitionItem
{ {
public string FunctionName { get; private set; } = string.Empty; public string FunctionName { get; private set; } = string.Empty;
public string FunctionDescription { 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 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 public static IEnumerable<FunctionDefinitionItem> DefinedFunctions
{ {
get 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.") } }; // Base
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 = "abs", Category = FunctionCategory.Base, FunctionDescription = "Absolute value of a number.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
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 = "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 = "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 = "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 = "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 = "max", Category = FunctionCategory.Base, FunctionDescription = "Maximum of two numbers.", FunctionArguments = new[] { new KeyValuePair<string, string>("first", "number"), new KeyValuePair<string, string>("second", "number") } };
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.") } }; // Angle
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 = "degrees", Category = FunctionCategory.Angle, FunctionDescription = "Converts radians to degrees.", FunctionArguments = new[] { new KeyValuePair<string, string>("value", "number") } };
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.") } }; 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 ArrowRightDropCircle = "\U000f0059";
public const string DecimalIncrease = "\U000f01b5"; public const string DecimalIncrease = "\U000f01b5";
public const string Function = "\U000f0295"; public const string Function = "\U000f0295";
public const string FunctionVariant = "\U000f070f";
public const string PlusCircle = "\U000f0417"; public const string PlusCircle = "\U000f0417";
public const string RoundedCorner = "\U000f0607"; public const string RoundedCorner = "\U000f0607";
public const string SquareRoot = "\U000f0784"; public const string SquareRoot = "\U000f0784";
@ -15,5 +16,10 @@ public static class IconFont
public const string MathTan = "\U000f0c98"; public const string MathTan = "\U000f0c98";
public const string MathLog = "\U000f1085"; public const string MathLog = "\U000f1085";
public const string CosineWave = "\U000f1479"; 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:IsZeroConverter x:Key="IsZeroConverter" />
<conv:WidthToSplitViewModeConverter x:Key="WidthToSplitViewModeConverter" /> <conv:WidthToSplitViewModeConverter x:Key="WidthToSplitViewModeConverter" />
<conv:WidthToPaneOpenConverter x:Key="WidthToPaneOpenConverter" /> <conv:WidthToPaneOpenConverter x:Key="WidthToPaneOpenConverter" />
<conv:CategoryToBrushConverter x:Key="CategoryToBrushConverter" />
</UserControl.Resources> </UserControl.Resources>
<!-- Replace columns with a responsive SplitView --> <!-- Replace columns with a responsive SplitView -->
<SplitView x:Name="RootSplit" <SplitView x:Name="RootSplit"
@ -132,50 +133,55 @@
<!-- Functions flyout anchored to this button --> <!-- Functions flyout anchored to this button -->
<Button Grid.Column="1" Command="{Binding ToggleFunctionsCommand}" Margin="0,0,6,0" MinHeight="40" MinWidth="40" <Button Grid.Column="1" Command="{Binding ToggleFunctionsCommand}" Margin="0,0,6,0" MinHeight="40" MinWidth="40"
AutomationProperties.Name="Toggle functions panel"> AutomationProperties.Name="Toggle functions panel">
<Button.Flyout> <Button.Flyout>
<Flyout IsOpen="{Binding IsFunctionsPanelOpen, Mode=TwoWay}" Placement="BottomEdgeAlignedLeft"> <Flyout IsOpen="{Binding IsFunctionsPanelOpen, Mode=TwoWay}" Placement="BottomEdgeAlignedLeft">
<Border Padding="8" MinWidth="320" MaxHeight="400"> <Border Padding="8" MinWidth="360" MaxHeight="420">
<ScrollViewer> <ScrollViewer>
<ListBox ItemsSource="{x:Static m:FunctionDefinitionItem.DefinedFunctions}" <StackPanel>
BorderThickness="0" <ItemsControl ItemsSource="{x:Static m:FunctionDefinitionItem.DefinedFunctionGroups}">
AutomationProperties.Name="Functions list"> <ItemsControl.ItemTemplate>
<ListBox.ItemTemplate> <DataTemplate x:DataType="m:FunctionGroup">
<DataTemplate x:DataType="m:FunctionDefinitionItem"> <StackPanel Margin="0,0,0,8">
<Button x:DataType="vm:MainViewModel" <!-- Group Header -->
DataContext="{Binding #Root.DataContext}" <Border Background="{Binding Category, Converter={StaticResource CategoryToBrushConverter}}"
Command="{Binding InsertFunctionCommand}" CornerRadius="6" Padding="8" Margin="0,0,0,6">
CommandParameter="{Binding $parent[ListBoxItem].DataContext}" <StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
Background="Transparent" BorderThickness="0" Padding="8" MinHeight="44" <TextBlock FontFamily="{StaticResource MDI}" Text="{Binding Icon}"/>
AutomationProperties.Name="Insert function"> <TextBlock Text="{Binding GroupName}" FontWeight="Bold"/>
<StackPanel Spacing="8" </StackPanel>
DataContext="{Binding $parent[ListBoxItem].DataContext}" </Border>
x:DataType="m:FunctionDefinitionItem">
<StackPanel Orientation="Horizontal" Spacing="8"> <!-- Group Items: simple rows -->
<TextBlock FontFamily="{StaticResource MDI}" Text="{Binding Icon}" /> <ListBox ItemsSource="{Binding Functions}" BorderThickness="0" SelectedIndex="-1">
<TextBlock Text="{Binding FunctionName}" FontWeight="Bold" /> <ListBox.ItemTemplate>
</StackPanel> <DataTemplate x:DataType="m:FunctionDefinitionItem">
<TextBlock Text="{Binding FunctionDescription}" FontStyle="Italic" TextWrapping="Wrap" /> <Button x:DataType="vm:MainViewModel"
<ItemsControl ItemsSource="{Binding FunctionArguments}"> DataContext="{Binding #Root.DataContext}"
<ItemsControl.ItemTemplate> Command="{Binding InsertFunctionCommand}"
<DataTemplate> CommandParameter="{Binding $parent[ListBoxItem].DataContext}"
<StackPanel> Background="Transparent" BorderThickness="0" Padding="8" MinHeight="36"
<TextBlock Text="{Binding Key}" /> AutomationProperties.Name="Insert function">
<TextBlock Text="{Binding Value}" FontStyle="Italic" Margin="8,0,0,0" /> <Grid ColumnDefinitions="*,Auto">
</StackPanel> <StackPanel>
</DataTemplate> <TextBlock Text="{Binding Signature}" FontWeight="Bold"/>
</ItemsControl.ItemTemplate> <TextBlock Text="{Binding FunctionDescription}" Opacity="0.8"/>
</ItemsControl> </StackPanel>
</StackPanel> </Grid>
</Button> </Button>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
</ScrollViewer> </StackPanel>
</Border> </DataTemplate>
</Flyout> </ItemsControl.ItemTemplate>
</Button.Flyout> </ItemsControl>
<TextBlock FontFamily="{StaticResource MDI}" Text="{x:Static m:IconFont.Function}" /> </StackPanel>
</Button> </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" <TextBox Grid.Column="2" Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinHeight="44"
AutomationProperties.Name="Expression input" AutomationProperties.Name="Expression input"