diff --git a/src/FlowForge.UI/App.axaml b/src/FlowForge.UI/App.axaml
index d6dbbc5..690bf90 100644
--- a/src/FlowForge.UI/App.axaml
+++ b/src/FlowForge.UI/App.axaml
@@ -6,7 +6,7 @@
-
+
diff --git a/src/FlowForge.UI/MainWindow.axaml b/src/FlowForge.UI/MainWindow.axaml
index 0ca7d19..fd57708 100644
--- a/src/FlowForge.UI/MainWindow.axaml
+++ b/src/FlowForge.UI/MainWindow.axaml
@@ -12,11 +12,11 @@
Width="1200" Height="700"
MinWidth="800" MinHeight="500"
Icon="avares://FlowForge.UI/Assets/icon.png"
- FontSize="14"
- Background="{DynamicResource MidnightBg}"
+ FontSize="13"
+ Background="{DynamicResource ForgeBg}"
KeyDown="OnKeyDown">
-
+
@@ -25,18 +25,18 @@
@@ -49,7 +49,7 @@
diff --git a/src/FlowForge.UI/Themes/MidnightTheme.axaml b/src/FlowForge.UI/Themes/MidnightTheme.axaml
deleted file mode 100644
index 34bab9c..0000000
--- a/src/FlowForge.UI/Themes/MidnightTheme.axaml
+++ /dev/null
@@ -1,193 +0,0 @@
-
-
-
-
-
-
-
- #0D1117
- #161B22
- #1C2128
- #21262D
-
-
- #30363D
- #484F58
-
-
- #E6EDF3
- #8B949E
- #484F58
-
-
- #58A6FF
- #58A6FF
- #3FB950
- #D29922
-
-
- #3FB950
- #F85149
- #D29922
- #58A6FF
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/FlowForge.UI/Themes/MoltenForgeTheme.axaml b/src/FlowForge.UI/Themes/MoltenForgeTheme.axaml
new file mode 100644
index 0000000..57534a0
--- /dev/null
+++ b/src/FlowForge.UI/Themes/MoltenForgeTheme.axaml
@@ -0,0 +1,325 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ #08070a
+ #0e0c11
+ #141218
+ #1c1820
+ #252029
+ #2e2735
+
+
+ #2a2230
+ #3d2f48
+
+
+ #ede6f0
+ #9a8da8
+ #564a62
+
+
+ #e8932f
+ #f5a623
+
+
+ #5bb8f5
+ #5ce0a0
+ #e8932f
+
+
+ #5ce0a0
+ #f2716a
+ #f5c842
+
+
+ #d4612c
+ #f5c842
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #f5f2ee
+ #eee9e3
+ #faf8f5
+ #ffffff
+ #f0ece6
+ #e8e3db
+
+
+ #ddd5ca
+ #c9bfb2
+
+
+ #1c1714
+ #6b5e52
+ #a89c8e
+
+
+ #d07a18
+ #e8932f
+
+
+ #2680c2
+ #1a9a6c
+ #d07a18
+
+
+ #1a9a6c
+ #d44040
+ #c88a15
+
+
+ #c05820
+ #e8b530
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/FlowForge.UI/ViewModels/MainWindowViewModel.cs b/src/FlowForge.UI/ViewModels/MainWindowViewModel.cs
index 3a44d60..5b4269f 100644
--- a/src/FlowForge.UI/ViewModels/MainWindowViewModel.cs
+++ b/src/FlowForge.UI/ViewModels/MainWindowViewModel.cs
@@ -370,6 +370,18 @@ private void Cancel()
_cts?.Cancel();
}
+ [RelayCommand]
+ private void Undo()
+ {
+ Editor.Undo();
+ }
+
+ [RelayCommand]
+ private void Redo()
+ {
+ Editor.Redo();
+ }
+
[RelayCommand]
private void LoadTemplate(string templateId)
{
diff --git a/src/FlowForge.UI/ViewModels/NodeLibraryGroupViewModel.cs b/src/FlowForge.UI/ViewModels/NodeLibraryGroupViewModel.cs
index e036265..1cbe1ae 100644
--- a/src/FlowForge.UI/ViewModels/NodeLibraryGroupViewModel.cs
+++ b/src/FlowForge.UI/ViewModels/NodeLibraryGroupViewModel.cs
@@ -1,15 +1,18 @@
using System.Collections.ObjectModel;
+using Avalonia.Media;
namespace FlowForge.UI.ViewModels;
public class NodeLibraryGroupViewModel : ViewModelBase
{
public string Category { get; }
+ public IBrush CategoryBrush { get; }
public ObservableCollection Items { get; }
- public NodeLibraryGroupViewModel(string category, ObservableCollection items)
+ public NodeLibraryGroupViewModel(string category, ObservableCollection items, IBrush categoryBrush)
{
Category = category;
Items = items;
+ CategoryBrush = categoryBrush;
}
}
diff --git a/src/FlowForge.UI/ViewModels/NodeLibraryItemViewModel.cs b/src/FlowForge.UI/ViewModels/NodeLibraryItemViewModel.cs
index a4884eb..e0797ec 100644
--- a/src/FlowForge.UI/ViewModels/NodeLibraryItemViewModel.cs
+++ b/src/FlowForge.UI/ViewModels/NodeLibraryItemViewModel.cs
@@ -1,13 +1,21 @@
+using Avalonia.Media;
+
namespace FlowForge.UI.ViewModels;
public class NodeLibraryItemViewModel : ViewModelBase
{
public string TypeKey { get; }
public string DisplayName { get; }
+ public string Icon { get; }
+ public IBrush IconBackground { get; }
+ public IBrush IconForeground { get; }
- public NodeLibraryItemViewModel(string typeKey, string displayName)
+ public NodeLibraryItemViewModel(string typeKey, string displayName, string icon, IBrush iconBackground, IBrush iconForeground)
{
TypeKey = typeKey;
DisplayName = displayName;
+ Icon = icon;
+ IconBackground = iconBackground;
+ IconForeground = iconForeground;
}
}
diff --git a/src/FlowForge.UI/ViewModels/NodeLibraryViewModel.cs b/src/FlowForge.UI/ViewModels/NodeLibraryViewModel.cs
index 418e970..1ca5ea3 100644
--- a/src/FlowForge.UI/ViewModels/NodeLibraryViewModel.cs
+++ b/src/FlowForge.UI/ViewModels/NodeLibraryViewModel.cs
@@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using FlowForge.Core.Execution;
@@ -16,6 +19,21 @@ public partial class NodeLibraryViewModel : ViewModelBase
["Output"] = "Save To"
};
+ private static readonly Dictionary NodeIcons = new()
+ {
+ ["FolderInput"] = "\U0001F4C1",
+ ["RenamePattern"] = "\u270E",
+ ["RenameRegex"] = ".*",
+ ["RenameAddAffix"] = "+a",
+ ["Filter"] = "\U0001F50D",
+ ["Sort"] = "\u21C5",
+ ["ImageResize"] = "\U0001F4F8",
+ ["ImageConvert"] = "\U0001F3A8",
+ ["ImageCompress"] = "\U0001F4E6",
+ ["MetadataExtract"] = "\U0001F4C4",
+ ["FolderOutput"] = "\U0001F4E5",
+ };
+
private List _allGroups = new();
[ObservableProperty]
@@ -34,6 +52,7 @@ public void Initialize(NodeRegistry registry)
Groups.Clear();
Dictionary> categoryItems = new();
+ Dictionary categoryKeys = new();
foreach (string typeKey in registry.GetRegisteredTypeKeys())
{
@@ -45,24 +64,58 @@ public void Initialize(NodeRegistry registry)
{
existingItems = new List();
categoryItems[categoryName] = existingItems;
+ categoryKeys[categoryName] = category;
}
- existingItems.Add(new NodeLibraryItemViewModel(typeKey, displayName));
+ string icon = NodeIcons.GetValueOrDefault(typeKey, "\u2699");
+ var brushes = GetCategoryBrushes(category);
+ existingItems.Add(new NodeLibraryItemViewModel(typeKey, displayName, icon, brushes.IconBg, brushes.IconFg));
}
- // Add groups in canonical order: Input, Process, Save To
string[] orderedCategories = ["Input", "Process", "Save To"];
foreach (string cat in orderedCategories)
{
if (categoryItems.TryGetValue(cat, out List? items))
{
- NodeLibraryGroupViewModel group = new(cat, new ObservableCollection(items));
+ IBrush categoryBrush = GetCategoryHeaderBrush(categoryKeys[cat]);
+ NodeLibraryGroupViewModel group = new(cat, new ObservableCollection(items), categoryBrush);
_allGroups.Add(group);
Groups.Add(group);
}
}
}
+ private static (IBrush IconBg, IBrush IconFg) GetCategoryBrushes(NodeCategory category)
+ {
+ return category switch
+ {
+ NodeCategory.Source => (GetBrush("ForgeSourceDim", "#145bb8f5"), GetBrush("ForgeSource", "#5bb8f5")),
+ NodeCategory.Transform => (GetBrush("ForgeTransformDim", "#145ce0a0"), GetBrush("ForgeTransform", "#5ce0a0")),
+ NodeCategory.Output => (GetBrush("ForgeOutputDim", "#14e8932f"), GetBrush("ForgeOutput", "#e8932f")),
+ _ => (GetBrush("ForgeElevated", "#252029"), GetBrush("ForgeTextMuted", "#564a62"))
+ };
+ }
+
+ private static IBrush GetCategoryHeaderBrush(NodeCategory category)
+ {
+ return category switch
+ {
+ NodeCategory.Source => GetBrush("ForgeSource", "#5bb8f5"),
+ NodeCategory.Transform => GetBrush("ForgeTransform", "#5ce0a0"),
+ NodeCategory.Output => GetBrush("ForgeOutput", "#e8932f"),
+ _ => GetBrush("ForgeTextMuted", "#564a62")
+ };
+ }
+
+ private static IBrush GetBrush(string key, string fallback)
+ {
+ if (Application.Current?.TryFindResource(key, Application.Current.ActualThemeVariant, out object? resource) == true && resource is IBrush brush)
+ {
+ return brush;
+ }
+ return new SolidColorBrush(Color.Parse(fallback));
+ }
+
private void FilterItems()
{
Groups.Clear();
@@ -85,7 +138,8 @@ private void FilterItems()
{
Groups.Add(new NodeLibraryGroupViewModel(
group.Category,
- new ObservableCollection(filtered)));
+ new ObservableCollection(filtered),
+ group.CategoryBrush));
}
}
}
diff --git a/src/FlowForge.UI/ViewModels/PipelineConnectorViewModel.cs b/src/FlowForge.UI/ViewModels/PipelineConnectorViewModel.cs
index cac2f0b..fc1107b 100644
--- a/src/FlowForge.UI/ViewModels/PipelineConnectorViewModel.cs
+++ b/src/FlowForge.UI/ViewModels/PipelineConnectorViewModel.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
namespace FlowForge.UI.ViewModels;
@@ -18,6 +19,9 @@ public partial class PipelineConnectorViewModel : ViewModelBase
public PipelineNodeViewModel Node { get; }
+ /// Category color for this connector (from parent node).
+ public IBrush ConnectorBrush => Node.CategoryBrush;
+
public PipelineConnectorViewModel(string title, bool isInput, PipelineNodeViewModel node)
{
_title = title;
diff --git a/src/FlowForge.UI/ViewModels/PipelineNodeViewModel.cs b/src/FlowForge.UI/ViewModels/PipelineNodeViewModel.cs
index ce4a7c2..383aba2 100644
--- a/src/FlowForge.UI/ViewModels/PipelineNodeViewModel.cs
+++ b/src/FlowForge.UI/ViewModels/PipelineNodeViewModel.cs
@@ -22,9 +22,75 @@ private static IBrush GetBrush(string key, string fallback)
return new SolidColorBrush(Color.Parse(fallback));
}
- private static readonly IBrush SourceBrush = GetBrush("MidnightSource", "#58A6FF");
- private static readonly IBrush TransformBrush = GetBrush("MidnightTransform", "#3FB950");
- private static readonly IBrush OutputBrush = GetBrush("MidnightOutput", "#D29922");
+ // Category solid colors (for text, connectors)
+ private static readonly IBrush SourceBrush = GetBrush("ForgeSource", "#5bb8f5");
+ private static readonly IBrush TransformBrush = GetBrush("ForgeTransform", "#5ce0a0");
+ private static readonly IBrush OutputBrush = GetBrush("ForgeOutput", "#e8932f");
+
+ // Category border colors (~20% opacity)
+ private static readonly IBrush SourceBorderBrush = new SolidColorBrush(Color.Parse("#335bb8f5"));
+ private static readonly IBrush TransformBorderBrush = new SolidColorBrush(Color.Parse("#335ce0a0"));
+ private static readonly IBrush OutputBorderBrush = new SolidColorBrush(Color.Parse("#33e8932f"));
+
+ // Category header gradients (subtle, not solid blocks)
+ private static readonly IBrush SourceHeaderGradient = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
+ GradientStops = { new GradientStop(Color.Parse("#0D5bb8f5"), 0), new GradientStop(Colors.Transparent, 1) }
+ };
+
+ private static readonly IBrush TransformHeaderGradient = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
+ GradientStops = { new GradientStop(Color.Parse("#0D5ce0a0"), 0), new GradientStop(Colors.Transparent, 1) }
+ };
+
+ private static readonly IBrush OutputHeaderGradient = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
+ GradientStops = { new GradientStop(Color.Parse("#0Ee8932f"), 0), new GradientStop(Colors.Transparent, 1) }
+ };
+
+ // Category-tinted gradient backgrounds
+ private static readonly IBrush SourceNodeBg = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
+ GradientStops = { new GradientStop(Color.Parse("#0F5bb8f5"), 0), new GradientStop(Color.Parse("#1c1820"), 0.5) }
+ };
+
+ private static readonly IBrush TransformNodeBg = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
+ GradientStops = { new GradientStop(Color.Parse("#0F5ce0a0"), 0), new GradientStop(Color.Parse("#1c1820"), 0.5) }
+ };
+
+ private static readonly IBrush OutputNodeBg = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
+ GradientStops = { new GradientStop(Color.Parse("#14e8932f"), 0), new GradientStop(Color.Parse("#1c1820"), 0.5) }
+ };
+
+ // Icon emoji per node type
+ private static readonly Dictionary NodeIcons = new()
+ {
+ ["FolderInput"] = "\U0001F4C1",
+ ["RenamePattern"] = "\u270E",
+ ["RenameRegex"] = ".*",
+ ["RenameAddAffix"] = "+a",
+ ["Filter"] = "\U0001F50D",
+ ["Sort"] = "\u21C5",
+ ["ImageResize"] = "\U0001F4F8",
+ ["ImageConvert"] = "\U0001F3A8",
+ ["ImageCompress"] = "\U0001F4E6",
+ ["MetadataExtract"] = "\U0001F4C4",
+ ["FolderOutput"] = "\U0001F4E5",
+ };
[ObservableProperty]
private Point _location;
@@ -35,12 +101,25 @@ private static IBrush GetBrush(string key, string fallback)
public Guid Id { get; }
public string TypeKey { get; }
public string Title { get; }
+ public string IconEmoji { get; }
+ public string ConfigPreview { get; }
public NodeCategory Category { get; }
public ObservableCollection Input { get; } = new();
public ObservableCollection Output { get; } = new();
public Dictionary Config { get; }
+
+ /// Solid category color for text and connector fills.
+ public IBrush CategoryBrush { get; }
+
+ /// Subtle gradient for the node header area.
public IBrush HeaderBrush { get; }
+ /// 20% opacity category color for the node border.
+ public IBrush NodeBorderBrush { get; }
+
+ /// Top-down gradient with subtle category tint.
+ public IBrush NodeBackground { get; }
+
public PipelineNodeViewModel(NodeDefinition definition, NodeRegistry registry)
{
Id = definition.Id;
@@ -48,9 +127,11 @@ public PipelineNodeViewModel(NodeDefinition definition, NodeRegistry registry)
Title = registry.GetDisplayName(definition.TypeKey);
Category = registry.GetCategoryForTypeKey(definition.TypeKey);
Config = definition.Config;
+ IconEmoji = NodeIcons.GetValueOrDefault(definition.TypeKey, "\u2699");
+ ConfigPreview = BuildConfigPreview(definition.Config);
_location = new Point(definition.Position.X, definition.Position.Y);
- HeaderBrush = Category switch
+ CategoryBrush = Category switch
{
NodeCategory.Source => SourceBrush,
NodeCategory.Transform => TransformBrush,
@@ -58,6 +139,30 @@ public PipelineNodeViewModel(NodeDefinition definition, NodeRegistry registry)
_ => TransformBrush
};
+ HeaderBrush = Category switch
+ {
+ NodeCategory.Source => SourceHeaderGradient,
+ NodeCategory.Transform => TransformHeaderGradient,
+ NodeCategory.Output => OutputHeaderGradient,
+ _ => TransformHeaderGradient
+ };
+
+ NodeBorderBrush = Category switch
+ {
+ NodeCategory.Source => SourceBorderBrush,
+ NodeCategory.Transform => TransformBorderBrush,
+ NodeCategory.Output => OutputBorderBrush,
+ _ => TransformBorderBrush
+ };
+
+ NodeBackground = Category switch
+ {
+ NodeCategory.Source => SourceNodeBg,
+ NodeCategory.Transform => TransformNodeBg,
+ NodeCategory.Output => OutputNodeBg,
+ _ => TransformNodeBg
+ };
+
// Source nodes have no input; output nodes have no output
if (Category != NodeCategory.Source)
{
@@ -69,4 +174,22 @@ public PipelineNodeViewModel(NodeDefinition definition, NodeRegistry registry)
Output.Add(new PipelineConnectorViewModel("Out", isInput: false, this));
}
}
+
+ private static string BuildConfigPreview(Dictionary config)
+ {
+ // Show the first string config value as a preview (path, pattern, etc.)
+ foreach (KeyValuePair kvp in config)
+ {
+ if (kvp.Value.ValueKind == JsonValueKind.String)
+ {
+ string val = kvp.Value.GetString() ?? string.Empty;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ return val;
+ }
+ }
+ }
+
+ return string.Empty;
+ }
}
diff --git a/src/FlowForge.UI/ViewModels/PropertiesViewModel.cs b/src/FlowForge.UI/ViewModels/PropertiesViewModel.cs
index 861be95..8d79255 100644
--- a/src/FlowForge.UI/ViewModels/PropertiesViewModel.cs
+++ b/src/FlowForge.UI/ViewModels/PropertiesViewModel.cs
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using FlowForge.Core.Execution;
using FlowForge.Core.Nodes.Base;
@@ -16,6 +19,12 @@ public partial class PropertiesViewModel : ViewModelBase
[ObservableProperty]
private bool _hasSelection;
+ [ObservableProperty]
+ private IBrush? _badgeForeground;
+
+ [ObservableProperty]
+ private IBrush? _badgeBackground;
+
public ObservableCollection Fields { get; } = new();
public void LoadNode(PipelineNodeViewModel? node, NodeRegistry registry, Action? onConfigChanged = null)
@@ -26,11 +35,21 @@ public void LoadNode(PipelineNodeViewModel? node, NodeRegistry registry, Action<
{
SelectedNodeTitle = null;
HasSelection = false;
+ BadgeForeground = null;
+ BadgeBackground = null;
return;
}
SelectedNodeTitle = node.Title;
HasSelection = true;
+ BadgeForeground = node.HeaderBrush;
+ BadgeBackground = node.Category switch
+ {
+ NodeCategory.Source => GetBrush("ForgeSourceDim", "#265bb8f5"),
+ NodeCategory.Transform => GetBrush("ForgeTransformDim", "#265ce0a0"),
+ NodeCategory.Output => GetBrush("ForgeOutputDim", "#26e8932f"),
+ _ => GetBrush("ForgeElevated", "#252029")
+ };
IReadOnlyList schema = registry.GetConfigSchema(node.TypeKey);
foreach (ConfigField field in schema)
@@ -38,4 +57,13 @@ public void LoadNode(PipelineNodeViewModel? node, NodeRegistry registry, Action<
Fields.Add(new ConfigFieldViewModel(field, node.Config, onConfigChanged));
}
}
+
+ private static IBrush GetBrush(string key, string fallback)
+ {
+ if (Application.Current?.TryFindResource(key, Application.Current.ActualThemeVariant, out object? resource) == true && resource is IBrush brush)
+ {
+ return brush;
+ }
+ return new SolidColorBrush(Color.Parse(fallback));
+ }
}
diff --git a/src/FlowForge.UI/Views/CanvasView.axaml b/src/FlowForge.UI/Views/CanvasView.axaml
index 71b91dc..cd91d4c 100644
--- a/src/FlowForge.UI/Views/CanvasView.axaml
+++ b/src/FlowForge.UI/Views/CanvasView.axaml
@@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FlowForge.UI.ViewModels"
xmlns:nodify="using:Nodify.Avalonia"
- xmlns:nodifyNodes="using:Nodify.Avalonia.Nodes"
xmlns:nodifyConn="using:Nodify.Avalonia.Connections"
mc:Ignorable="d"
d:DesignWidth="600" d:DesignHeight="400"
@@ -18,43 +17,133 @@
PendingConnection="{Binding PendingConnection}"
ItemsDragStartedCommand="{Binding ItemsDragStartedCommand}"
ItemsDragCompletedCommand="{Binding ItemsDragCompletedCommand}"
- Background="{DynamicResource MidnightBg}"
+ Background="{DynamicResource ForgeBg}"
+ GridCellSize="20"
DragDrop.AllowDrop="True">
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Target="{Binding Target.Anchor}"
+ Stroke="{DynamicResource ForgeAccent}"
+ StrokeThickness="2" />
@@ -73,54 +162,82 @@
+
+
+
-
+ Spacing="20"
+ MaxWidth="400">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+ Click="OnTemplateButtonClick">
+
+
+
+
+
+ Click="OnTemplateButtonClick">
+
+
+
+
+
+ Click="OnTemplateButtonClick">
+
+
+
+
+
-
+ Click="OnTemplateButtonClick">
+
+
+
+
+
+
diff --git a/src/FlowForge.UI/Views/ConfigFieldTemplateSelector.cs b/src/FlowForge.UI/Views/ConfigFieldTemplateSelector.cs
index cf5c86f..4f882b0 100644
--- a/src/FlowForge.UI/Views/ConfigFieldTemplateSelector.cs
+++ b/src/FlowForge.UI/Views/ConfigFieldTemplateSelector.cs
@@ -26,10 +26,10 @@ public Control Build(object? param)
Border cardBorder = new()
{
- Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#1C2128")),
- BorderBrush = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#30363D")),
+ Background = GetThemeBrush("ForgeDeep", "#0e0c11"),
+ BorderBrush = GetThemeBrush("ForgeBorder", "#2a2230"),
BorderThickness = new Avalonia.Thickness(1),
- CornerRadius = new Avalonia.CornerRadius(6),
+ CornerRadius = new Avalonia.CornerRadius(12),
Padding = new Avalonia.Thickness(12, 8),
Margin = new Avalonia.Thickness(0, 0, 0, 8)
};
@@ -44,7 +44,7 @@ public Control Build(object? param)
Text = field.IsRequired ? $"{field.Label} *" : field.Label,
FontWeight = Avalonia.Media.FontWeight.SemiBold,
FontSize = 12,
- Foreground = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#E6EDF3")),
+ Foreground = GetThemeBrush("ForgeText", "#ede6f0"),
Margin = new Avalonia.Thickness(0, 0, 0, 2)
};
panel.Children.Add(label);
@@ -255,4 +255,14 @@ private static TextBox BuildMultiLineEditor(ConfigFieldViewModel field)
new Avalonia.Data.Binding("Value"));
return textBox;
}
+
+ private static Avalonia.Media.IBrush GetThemeBrush(string key, string fallback)
+ {
+ if (Avalonia.Application.Current?.TryFindResource(key, Avalonia.Application.Current.ActualThemeVariant, out object? resource) == true && resource is Avalonia.Media.IBrush brush)
+ {
+ return brush;
+ }
+
+ return new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse(fallback));
+ }
}
diff --git a/src/FlowForge.UI/Views/ExecutionLogView.axaml b/src/FlowForge.UI/Views/ExecutionLogView.axaml
index 8c782b5..e5e1936 100644
--- a/src/FlowForge.UI/Views/ExecutionLogView.axaml
+++ b/src/FlowForge.UI/Views/ExecutionLogView.axaml
@@ -8,10 +8,23 @@
x:Class="FlowForge.UI.Views.ExecutionLogView"
x:DataType="vm:ExecutionLogViewModel">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -21,12 +34,12 @@
Command="{Binding SelectTabCommand}"
CommandParameter="0">
-
-
+
+ Foreground="{DynamicResource ForgeSuccess}"
+ FontSize="10" FontWeight="Bold" />
@@ -38,12 +51,12 @@
Command="{Binding SelectTabCommand}"
CommandParameter="1">
-
-
+
+ Foreground="{DynamicResource ForgeError}"
+ FontSize="10" FontWeight="Bold" />
@@ -55,12 +68,12 @@
Command="{Binding SelectTabCommand}"
CommandParameter="2">
-
-
+
+ Foreground="{DynamicResource ForgeWarning}"
+ FontSize="10" FontWeight="Bold" />
@@ -71,8 +84,8 @@
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"
HorizontalAlignment="Right"
- Foreground="{DynamicResource MidnightTextSecondary}"
- FontSize="15"
+ Foreground="{DynamicResource ForgeTextSecondary}"
+ FontSize="12"
Margin="8,0,12,0"
IsVisible="{Binding !IsRunning}" />
@@ -81,29 +94,30 @@
+ Foreground="{DynamicResource ForgeAccent}"
+ FontWeight="SemiBold" FontSize="12" />
+ Foreground="{DynamicResource ForgeTextSecondary}"
+ FontSize="12" />
+ Height="4" Margin="0,6,0,0"
+ CornerRadius="2" />
@@ -116,41 +130,53 @@
-
-
+
+
+
+
+
-
-
@@ -167,8 +193,8 @@
@@ -176,27 +202,30 @@
-
-
+
+
-
-
@@ -212,8 +241,8 @@
@@ -221,27 +250,30 @@
-
-
+
+
-
-
diff --git a/src/FlowForge.UI/Views/NodeLibraryView.axaml b/src/FlowForge.UI/Views/NodeLibraryView.axaml
index 62c93d6..9dc300b 100644
--- a/src/FlowForge.UI/Views/NodeLibraryView.axaml
+++ b/src/FlowForge.UI/Views/NodeLibraryView.axaml
@@ -4,60 +4,84 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FlowForge.UI.ViewModels"
mc:Ignorable="d"
- d:DesignWidth="200" d:DesignHeight="600"
+ d:DesignWidth="250" d:DesignHeight="600"
x:Class="FlowForge.UI.Views.NodeLibraryView"
x:DataType="vm:NodeLibraryViewModel">
-
-
+
+
+
+
+
+
-
-
-
+
+ FontWeight="Bold"
+ LetterSpacing="1.2"
+ Foreground="{DynamicResource ForgeTextMuted}"
+ Margin="0,0,0,2" />
+ Watermark="Search nodes..."
+ Background="{DynamicResource ForgeDeep}"
+ CornerRadius="12"
+ Margin="10,10,10,4" />
-
+
+
+
+
diff --git a/src/FlowForge.UI/Views/PropertiesView.axaml b/src/FlowForge.UI/Views/PropertiesView.axaml
index 2173eb7..b586616 100644
--- a/src/FlowForge.UI/Views/PropertiesView.axaml
+++ b/src/FlowForge.UI/Views/PropertiesView.axaml
@@ -5,49 +5,47 @@
xmlns:vm="using:FlowForge.UI.ViewModels"
xmlns:views="using:FlowForge.UI.Views"
mc:Ignorable="d"
- d:DesignWidth="250" d:DesignHeight="600"
+ d:DesignWidth="290" d:DesignHeight="600"
x:Class="FlowForge.UI.Views.PropertiesView"
x:DataType="vm:PropertiesViewModel">
-
-
-
+
+
+
+
+
-
-
+ Padding="10">
diff --git a/src/FlowForge.UI/Views/ShortcutsWindow.axaml b/src/FlowForge.UI/Views/ShortcutsWindow.axaml
index 416bc86..16a0c92 100644
--- a/src/FlowForge.UI/Views/ShortcutsWindow.axaml
+++ b/src/FlowForge.UI/Views/ShortcutsWindow.axaml
@@ -2,69 +2,77 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="FlowForge.UI.Views.ShortcutsWindow"
Title="Keyboard Shortcuts"
- Width="400" Height="400"
+ Width="420" Height="440"
CanResize="False"
WindowStartupLocation="CenterOwner"
- Background="{DynamicResource MidnightPanel}">
+ Background="{DynamicResource ForgePanel}">
+ Foreground="{DynamicResource ForgeAccent}" />
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+
diff --git a/src/FlowForge.UI/Views/ToolbarView.axaml b/src/FlowForge.UI/Views/ToolbarView.axaml
index d8df2f8..0f3ba53 100644
--- a/src/FlowForge.UI/Views/ToolbarView.axaml
+++ b/src/FlowForge.UI/Views/ToolbarView.axaml
@@ -4,123 +4,216 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FlowForge.UI.ViewModels"
mc:Ignorable="d"
- d:DesignWidth="800" d:DesignHeight="40"
+ d:DesignWidth="800" d:DesignHeight="50"
x:Class="FlowForge.UI.Views.ToolbarView"
x:DataType="vm:MainWindowViewModel">
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
-
+
+
+
+
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+