Improve the filename format input field's appearance and validation

This commit is contained in:
whimsical-c4lic0 2026-02-17 13:58:06 -06:00
parent b6872a2b9e
commit e58ac7d2a6
4 changed files with 516 additions and 135 deletions

View File

@ -0,0 +1,224 @@
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.VisualTree;
using OF_DL.Gui.ViewModels;
namespace OF_DL.Gui.Controls;
public class FileNameFormatOverlayTextBlock : TextBlock
{
public static readonly StyledProperty<IEnumerable<FileNameFormatSegmentViewModel>?> SegmentsProperty =
AvaloniaProperty.Register<FileNameFormatOverlayTextBlock, IEnumerable<FileNameFormatSegmentViewModel>?>(nameof(Segments));
public static readonly StyledProperty<TextBox?> SourceTextBoxProperty =
AvaloniaProperty.Register<FileNameFormatOverlayTextBlock, TextBox?>(nameof(SourceTextBox));
private INotifyCollectionChanged? _segmentsCollection;
private TextBox? _attachedTextBox;
private ScrollViewer? _attachedScrollViewer;
static FileNameFormatOverlayTextBlock()
{
SegmentsProperty.Changed.AddClassHandler<FileNameFormatOverlayTextBlock>(OnSegmentsChanged);
SourceTextBoxProperty.Changed.AddClassHandler<FileNameFormatOverlayTextBlock>(OnSourceTextBoxChanged);
}
public IEnumerable<FileNameFormatSegmentViewModel>? Segments
{
get => GetValue(SegmentsProperty);
set => SetValue(SegmentsProperty, value);
}
public TextBox? SourceTextBox
{
get => GetValue(SourceTextBoxProperty);
set => SetValue(SourceTextBoxProperty, value);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
DetachSegmentsCollection();
AttachSegmentsCollection(Segments);
AttachSourceTextBox(SourceTextBox);
RebuildInlines();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DetachSourceTextBox();
DetachSegmentsCollection();
base.OnDetachedFromVisualTree(e);
}
private static void OnSegmentsChanged(
FileNameFormatOverlayTextBlock sender,
AvaloniaPropertyChangedEventArgs e)
{
sender.DetachSegmentsCollection();
sender.AttachSegmentsCollection(e.NewValue as IEnumerable<FileNameFormatSegmentViewModel>);
sender.RebuildInlines();
}
private static void OnSourceTextBoxChanged(
FileNameFormatOverlayTextBlock sender,
AvaloniaPropertyChangedEventArgs e)
{
sender.AttachSourceTextBox(e.NewValue as TextBox);
}
private void AttachSegmentsCollection(IEnumerable<FileNameFormatSegmentViewModel>? segments)
{
_segmentsCollection = segments as INotifyCollectionChanged;
if (_segmentsCollection is not null)
{
_segmentsCollection.CollectionChanged += OnSegmentsCollectionChanged;
}
}
private void DetachSegmentsCollection()
{
if (_segmentsCollection is null)
{
return;
}
_segmentsCollection.CollectionChanged -= OnSegmentsCollectionChanged;
_segmentsCollection = null;
}
private void OnSegmentsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
RebuildInlines();
}
private void AttachSourceTextBox(TextBox? textBox)
{
if (ReferenceEquals(_attachedTextBox, textBox))
{
UpdateOverlayOffset();
return;
}
DetachSourceTextBox();
_attachedTextBox = textBox;
if (_attachedTextBox is null)
{
UpdateOverlayOffset();
return;
}
_attachedTextBox.TemplateApplied += OnSourceTextBoxTemplateApplied;
AttachScrollViewer(FindSourceScrollViewer(_attachedTextBox));
UpdateOverlayOffset();
}
private void DetachSourceTextBox()
{
if (_attachedTextBox is not null)
{
_attachedTextBox.TemplateApplied -= OnSourceTextBoxTemplateApplied;
}
if (_attachedScrollViewer is not null)
{
_attachedScrollViewer.ScrollChanged -= OnSourceScrollChanged;
_attachedScrollViewer = null;
}
_attachedTextBox = null;
UpdateOverlayOffset();
}
private void OnSourceTextBoxTemplateApplied(object? sender, TemplateAppliedEventArgs e)
{
ScrollViewer? scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
AttachScrollViewer(scrollViewer ?? FindSourceScrollViewer(_attachedTextBox));
UpdateOverlayOffset();
}
private void OnSourceScrollChanged(object? sender, ScrollChangedEventArgs e)
{
UpdateOverlayOffset();
}
private void AttachScrollViewer(ScrollViewer? scrollViewer)
{
if (ReferenceEquals(_attachedScrollViewer, scrollViewer))
{
return;
}
if (_attachedScrollViewer is not null)
{
_attachedScrollViewer.ScrollChanged -= OnSourceScrollChanged;
}
_attachedScrollViewer = scrollViewer;
if (_attachedScrollViewer is not null)
{
_attachedScrollViewer.ScrollChanged += OnSourceScrollChanged;
}
}
private void UpdateOverlayOffset()
{
if (_attachedScrollViewer is null)
{
RenderTransform = null;
return;
}
RenderTransform = new TranslateTransform(-_attachedScrollViewer.Offset.X, -_attachedScrollViewer.Offset.Y);
}
private static ScrollViewer? FindSourceScrollViewer(TextBox? textBox)
{
if (textBox is null)
{
return null;
}
return textBox.GetVisualDescendants().OfType<ScrollViewer>().FirstOrDefault();
}
private void RebuildInlines()
{
if (Inlines is null)
{
return;
}
Inlines.Clear();
if (Segments is null)
{
return;
}
foreach (FileNameFormatSegmentViewModel segment in Segments)
{
Run run = new()
{
Text = segment.Text ?? string.Empty,
Foreground = ParseForegroundBrush(segment.Foreground)
};
Inlines.Add(run);
}
}
private IBrush ParseForegroundBrush(string? value)
{
if (!string.IsNullOrWhiteSpace(value) && Color.TryParse(value, out Color color))
{
return new SolidColorBrush(color);
}
return Foreground ?? Brushes.Transparent;
}
}

View File

@ -140,7 +140,8 @@ public partial class ConfigFieldViewModel : ViewModelBase
public bool IsTimeoutField =>
string.Equals(PropertyName, nameof(Config.Timeout), StringComparison.Ordinal);
public bool IsRegularTextInput => IsTextInput && !IsIgnoredUsersListField && !IsCreatorConfigsField;
public bool IsRegularTextInput =>
IsTextInput && !IsIgnoredUsersListField && !IsCreatorConfigsField && !IsFileNameFormatField;
public bool HasHelpText => !string.IsNullOrWhiteSpace(HelpText);
@ -167,6 +168,8 @@ public partial class ConfigFieldViewModel : ViewModelBase
[ObservableProperty]
private string _textValue = string.Empty;
private bool _isNormalizingFileNameFormatInput;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(InsertSelectedFileNameVariableCommand))]
private string? _selectedFileNameVariable;
@ -364,6 +367,18 @@ public partial class ConfigFieldViewModel : ViewModelBase
partial void OnTextValueChanged(string value)
{
if (IsFileNameFormatField && !_isNormalizingFileNameFormatInput)
{
string trimmedValue = value.Trim();
if (!string.Equals(value, trimmedValue, StringComparison.Ordinal))
{
_isNormalizingFileNameFormatInput = true;
TextValue = trimmedValue;
_isNormalizingFileNameFormatInput = false;
return;
}
}
// Store actual value if not the privacy placeholder
if (value != "[Hidden for Privacy]")
{

View File

@ -18,12 +18,16 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
private readonly Action<bool> _onClose;
private readonly Func<bool> _isUsernameDuplicate;
private bool _isNormalizingUsername;
private bool _isNormalizingFileNameFormat;
[ObservableProperty] private bool _isOpen;
[ObservableProperty] private bool _isEditMode;
[ObservableProperty] private string _originalUsername = string.Empty;
[ObservableProperty] private string _username = string.Empty;
[ObservableProperty] private string _usernameError = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasUsernameError))]
private string _usernameError = string.Empty;
[ObservableProperty] private string _paidPostFileNameFormat = string.Empty;
[ObservableProperty] private string _postFileNameFormat = string.Empty;
[ObservableProperty] private string _paidMessageFileNameFormat = string.Empty;
@ -32,10 +36,37 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
[ObservableProperty] private string _selectedPostVariable = string.Empty;
[ObservableProperty] private string _selectedPaidMessageVariable = string.Empty;
[ObservableProperty] private string _selectedMessageVariable = string.Empty;
[ObservableProperty] private string _unknownPaidPostVariablesMessage = string.Empty;
[ObservableProperty] private string _unknownPostVariablesMessage = string.Empty;
[ObservableProperty] private string _unknownPaidMessageVariablesMessage = string.Empty;
[ObservableProperty] private string _unknownMessageVariablesMessage = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasUnknownPaidPostVariables))]
private string _unknownPaidPostVariablesMessage = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasUnknownPostVariables))]
private string _unknownPostVariablesMessage = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasUnknownPaidMessageVariables))]
private string _unknownPaidMessageVariablesMessage = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasUnknownMessageVariables))]
private string _unknownMessageVariablesMessage = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasPaidPostFileNameFormatError))]
private string _paidPostFileNameFormatError = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasPostFileNameFormatError))]
private string _postFileNameFormatError = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasPaidMessageFileNameFormatError))]
private string _paidMessageFileNameFormatError = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasMessageFileNameFormatError))]
private string _messageFileNameFormatError = string.Empty;
public ObservableCollection<string> AvailableUsers { get; }
public ObservableCollection<string> PaidPostVariables { get; } = [];
@ -52,6 +83,10 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
public bool HasUnknownPostVariables => !string.IsNullOrWhiteSpace(UnknownPostVariablesMessage);
public bool HasUnknownPaidMessageVariables => !string.IsNullOrWhiteSpace(UnknownPaidMessageVariablesMessage);
public bool HasUnknownMessageVariables => !string.IsNullOrWhiteSpace(UnknownMessageVariablesMessage);
public bool HasPaidPostFileNameFormatError => !string.IsNullOrWhiteSpace(PaidPostFileNameFormatError);
public bool HasPostFileNameFormatError => !string.IsNullOrWhiteSpace(PostFileNameFormatError);
public bool HasPaidMessageFileNameFormatError => !string.IsNullOrWhiteSpace(PaidMessageFileNameFormatError);
public bool HasMessageFileNameFormatError => !string.IsNullOrWhiteSpace(MessageFileNameFormatError);
public string DialogTitle => IsEditMode ? "Edit Creator Config" : "Add Creator Config";
@ -85,11 +120,11 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
IsEditMode = false;
OriginalUsername = string.Empty;
Username = string.Empty;
UsernameError = string.Empty;
PaidPostFileNameFormat = string.Empty;
PostFileNameFormat = string.Empty;
PaidMessageFileNameFormat = string.Empty;
MessageFileNameFormat = string.Empty;
ClearValidationErrors();
ClearAllPreviews();
Log.Information("About to set IsOpen = true");
IsOpen = true;
@ -101,11 +136,11 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
IsEditMode = true;
OriginalUsername = username;
Username = username;
UsernameError = string.Empty;
PaidPostFileNameFormat = config.PaidPostFileNameFormat ?? string.Empty;
PostFileNameFormat = config.PostFileNameFormat ?? string.Empty;
PaidMessageFileNameFormat = config.PaidMessageFileNameFormat ?? string.Empty;
MessageFileNameFormat = config.MessageFileNameFormat ?? string.Empty;
ClearValidationErrors();
UpdateAllPreviews();
IsOpen = true;
}
@ -184,32 +219,148 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
Log.Information("*** IsOpen property changed to: {Value} ***", value);
}
partial void OnPaidPostFileNameFormatChanged(string value) => UpdatePaidPostPreview();
partial void OnPostFileNameFormatChanged(string value) => UpdatePostPreview();
partial void OnPaidMessageFileNameFormatChanged(string value) => UpdatePaidMessagePreview();
partial void OnMessageFileNameFormatChanged(string value) => UpdateMessagePreview();
partial void OnUsernameChanged(string value)
{
if (_isNormalizingUsername)
{
return;
}
string trimmed = value.Trim();
if (!string.Equals(value, trimmed, StringComparison.Ordinal))
{
_isNormalizingUsername = true;
Username = trimmed;
_isNormalizingUsername = false;
}
UsernameError = string.Empty;
}
partial void OnPaidPostFileNameFormatChanged(string value) =>
NormalizeFileNameFormat(
value,
trimmed => PaidPostFileNameFormat = trimmed,
() => PaidPostFileNameFormatError = string.Empty,
UpdatePaidPostPreview);
partial void OnPostFileNameFormatChanged(string value) =>
NormalizeFileNameFormat(
value,
trimmed => PostFileNameFormat = trimmed,
() => PostFileNameFormatError = string.Empty,
UpdatePostPreview);
partial void OnPaidMessageFileNameFormatChanged(string value) =>
NormalizeFileNameFormat(
value,
trimmed => PaidMessageFileNameFormat = trimmed,
() => PaidMessageFileNameFormatError = string.Empty,
UpdatePaidMessagePreview);
partial void OnMessageFileNameFormatChanged(string value) =>
NormalizeFileNameFormat(
value,
trimmed => MessageFileNameFormat = trimmed,
() => MessageFileNameFormatError = string.Empty,
UpdateMessagePreview);
private bool Validate()
{
UsernameError = string.Empty;
ClearValidationErrors();
TrimInputValues();
bool isValid = true;
if (string.IsNullOrWhiteSpace(Username))
{
UsernameError = "Username is required.";
return false;
isValid = false;
}
string trimmedUsername = Username.Trim();
if (!IsEditMode || trimmedUsername != OriginalUsername)
if (isValid && (!IsEditMode || trimmedUsername != OriginalUsername))
{
if (_isUsernameDuplicate())
{
UsernameError = "A config for this username already exists.";
return false;
isValid = false;
}
}
return true;
ValidateFileNameFormatUniqueness(PaidPostFileNameFormat, message => PaidPostFileNameFormatError = message,
ref isValid);
ValidateFileNameFormatUniqueness(PostFileNameFormat, message => PostFileNameFormatError = message,
ref isValid);
ValidateFileNameFormatUniqueness(PaidMessageFileNameFormat, message => PaidMessageFileNameFormatError = message,
ref isValid);
ValidateFileNameFormatUniqueness(MessageFileNameFormat, message => MessageFileNameFormatError = message,
ref isValid);
return isValid;
}
private void ClearValidationErrors()
{
UsernameError = string.Empty;
PaidPostFileNameFormatError = string.Empty;
PostFileNameFormatError = string.Empty;
PaidMessageFileNameFormatError = string.Empty;
MessageFileNameFormatError = string.Empty;
}
private void TrimInputValues()
{
Username = Username.Trim();
PaidPostFileNameFormat = PaidPostFileNameFormat.Trim();
PostFileNameFormat = PostFileNameFormat.Trim();
PaidMessageFileNameFormat = PaidMessageFileNameFormat.Trim();
MessageFileNameFormat = MessageFileNameFormat.Trim();
}
private static void ValidateFileNameFormatUniqueness(
string format,
Action<string> setError,
ref bool isValid)
{
if (string.IsNullOrWhiteSpace(format))
{
setError(string.Empty);
return;
}
bool hasUniqueToken = format.Contains("{mediaId}", StringComparison.OrdinalIgnoreCase) ||
format.Contains("{filename}", StringComparison.OrdinalIgnoreCase);
if (hasUniqueToken)
{
setError(string.Empty);
return;
}
setError("Format must include {mediaId} or {filename} to avoid file collisions.");
isValid = false;
}
private void NormalizeFileNameFormat(
string value,
Action<string> setValue,
Action clearError,
Action updatePreview)
{
if (!_isNormalizingFileNameFormat)
{
string trimmedValue = value.Trim();
if (!string.Equals(value, trimmedValue, StringComparison.Ordinal))
{
_isNormalizingFileNameFormat = true;
setValue(trimmedValue);
_isNormalizingFileNameFormat = false;
return;
}
}
clearError();
updatePreview();
}
private void ClearAllPreviews()

View File

@ -2,6 +2,7 @@
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:controls="using:OF_DL.Gui.Controls"
xmlns:vm="using:OF_DL.Gui.ViewModels"
xmlns:views="using:OF_DL.Gui.Views"
x:Class="OF_DL.Gui.Views.MainWindow"
@ -38,6 +39,10 @@
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
<Style Selector="TextBox.fileNameOverlayInput:not(:empty)">
<Setter Property="Foreground" Value="Transparent" />
</Style>
</Window.Styles>
<Grid RowDefinitions="Auto,Auto,*">
@ -646,6 +651,26 @@
<StackPanel
IsVisible="{Binding IsFileNameFormatField}"
Spacing="6">
<Grid ClipToBounds="True">
<TextBox
x:Name="ConfigFileNameFormatTextBox"
Classes="fileNameOverlayInput"
HorizontalAlignment="Stretch"
Text="{Binding TextValue}"
Padding="8,6"
CaretBrush="{DynamicResource TextPrimaryBrush}" />
<controls:FileNameFormatOverlayTextBlock
IsHitTestVisible="False"
Margin="{Binding #ConfigFileNameFormatTextBox.Padding}"
VerticalAlignment="Center"
Segments="{Binding FileNameFormatSegments}"
SourceTextBox="{Binding #ConfigFileNameFormatTextBox}"
FontFamily="{Binding #ConfigFileNameFormatTextBox.FontFamily}"
FontSize="{Binding #ConfigFileNameFormatTextBox.FontSize}"
FontWeight="{Binding #ConfigFileNameFormatTextBox.FontWeight}"
FontStyle="{Binding #ConfigFileNameFormatTextBox.FontStyle}" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<ComboBox Grid.Column="0"
HorizontalAlignment="Stretch"
@ -658,32 +683,6 @@
Command="{Binding InsertSelectedFileNameVariableCommand}" />
</Grid>
<Border Padding="8"
Background="{DynamicResource PreviewBackgroundBrush}"
BorderBrush="{DynamicResource PreviewBorderBrush}"
BorderThickness="1"
CornerRadius="8">
<ItemsControl
ItemsSource="{Binding FileNameFormatSegments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate
x:DataType="vm:FileNameFormatSegmentViewModel">
<TextBlock
Text="{Binding Text}"
Foreground="{Binding Foreground}"
FontFamily="Consolas"
TextWrapping="Wrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<TextBlock
IsVisible="{Binding HasUnknownFileNameVariables}"
Foreground="{DynamicResource ErrorTextBrush}"
@ -992,8 +991,23 @@
<StackPanel Spacing="8">
<TextBlock FontWeight="SemiBold" Foreground="{DynamicResource TextPrimaryBrush}" Text="Paid Post Filename Format" />
<TextBox Text="{Binding PaidPostFileNameFormat}"
Watermark="Optional: override global paid post format" />
<Grid ClipToBounds="True">
<TextBox x:Name="PaidPostFileNameFormatTextBox"
Classes="fileNameOverlayInput"
Text="{Binding PaidPostFileNameFormat}"
Watermark="Optional: override global paid post format"
Padding="8,6"
CaretBrush="{DynamicResource TextPrimaryBrush}" />
<controls:FileNameFormatOverlayTextBlock IsHitTestVisible="False"
Margin="{Binding #PaidPostFileNameFormatTextBox.Padding}"
VerticalAlignment="Center"
Segments="{Binding PaidPostSegments}"
SourceTextBox="{Binding #PaidPostFileNameFormatTextBox}"
FontFamily="{Binding #PaidPostFileNameFormatTextBox.FontFamily}"
FontSize="{Binding #PaidPostFileNameFormatTextBox.FontSize}"
FontWeight="{Binding #PaidPostFileNameFormatTextBox.FontWeight}"
FontStyle="{Binding #PaidPostFileNameFormatTextBox.FontStyle}" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<ComboBox Grid.Column="0"
HorizontalAlignment="Stretch"
@ -1005,37 +1019,35 @@
Content="Insert"
Command="{Binding InsertPaidPostVariableCommand}" />
</Grid>
<Border Padding="8"
Background="{DynamicResource PreviewBackgroundBrush}"
BorderBrush="{DynamicResource PreviewBorderBrush}"
BorderThickness="1"
CornerRadius="8">
<ItemsControl ItemsSource="{Binding PaidPostSegments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:FileNameFormatSegmentViewModel">
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
FontFamily="Consolas"
TextWrapping="Wrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<TextBlock IsVisible="{Binding HasUnknownPaidPostVariables}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding UnknownPaidPostVariablesMessage}"
TextWrapping="Wrap" />
<TextBlock IsVisible="{Binding HasPaidPostFileNameFormatError}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding PaidPostFileNameFormatError}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel Spacing="8">
<TextBlock FontWeight="SemiBold" Foreground="{DynamicResource TextPrimaryBrush}" Text="Post Filename Format" />
<TextBox Text="{Binding PostFileNameFormat}"
Watermark="Optional: override global post format" />
<Grid ClipToBounds="True">
<TextBox x:Name="PostFileNameFormatTextBox"
Classes="fileNameOverlayInput"
Text="{Binding PostFileNameFormat}"
Watermark="Optional: override global post format"
Padding="8,6"
CaretBrush="{DynamicResource TextPrimaryBrush}" />
<controls:FileNameFormatOverlayTextBlock IsHitTestVisible="False"
Margin="{Binding #PostFileNameFormatTextBox.Padding}"
VerticalAlignment="Center"
Segments="{Binding PostSegments}"
SourceTextBox="{Binding #PostFileNameFormatTextBox}"
FontFamily="{Binding #PostFileNameFormatTextBox.FontFamily}"
FontSize="{Binding #PostFileNameFormatTextBox.FontSize}"
FontWeight="{Binding #PostFileNameFormatTextBox.FontWeight}"
FontStyle="{Binding #PostFileNameFormatTextBox.FontStyle}" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<ComboBox Grid.Column="0"
HorizontalAlignment="Stretch"
@ -1047,37 +1059,35 @@
Content="Insert"
Command="{Binding InsertPostVariableCommand}" />
</Grid>
<Border Padding="8"
Background="{DynamicResource PreviewBackgroundBrush}"
BorderBrush="{DynamicResource PreviewBorderBrush}"
BorderThickness="1"
CornerRadius="8">
<ItemsControl ItemsSource="{Binding PostSegments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:FileNameFormatSegmentViewModel">
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
FontFamily="Consolas"
TextWrapping="Wrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<TextBlock IsVisible="{Binding HasUnknownPostVariables}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding UnknownPostVariablesMessage}"
TextWrapping="Wrap" />
<TextBlock IsVisible="{Binding HasPostFileNameFormatError}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding PostFileNameFormatError}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel Spacing="8">
<TextBlock FontWeight="SemiBold" Foreground="{DynamicResource TextPrimaryBrush}" Text="Paid Message Filename Format" />
<TextBox Text="{Binding PaidMessageFileNameFormat}"
Watermark="Optional: override global paid message format" />
<Grid ClipToBounds="True">
<TextBox x:Name="PaidMessageFileNameFormatTextBox"
Classes="fileNameOverlayInput"
Text="{Binding PaidMessageFileNameFormat}"
Watermark="Optional: override global paid message format"
Padding="8,6"
CaretBrush="{DynamicResource TextPrimaryBrush}" />
<controls:FileNameFormatOverlayTextBlock IsHitTestVisible="False"
Margin="{Binding #PaidMessageFileNameFormatTextBox.Padding}"
VerticalAlignment="Center"
Segments="{Binding PaidMessageSegments}"
SourceTextBox="{Binding #PaidMessageFileNameFormatTextBox}"
FontFamily="{Binding #PaidMessageFileNameFormatTextBox.FontFamily}"
FontSize="{Binding #PaidMessageFileNameFormatTextBox.FontSize}"
FontWeight="{Binding #PaidMessageFileNameFormatTextBox.FontWeight}"
FontStyle="{Binding #PaidMessageFileNameFormatTextBox.FontStyle}" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<ComboBox Grid.Column="0"
HorizontalAlignment="Stretch"
@ -1089,37 +1099,35 @@
Content="Insert"
Command="{Binding InsertPaidMessageVariableCommand}" />
</Grid>
<Border Padding="8"
Background="{DynamicResource PreviewBackgroundBrush}"
BorderBrush="{DynamicResource PreviewBorderBrush}"
BorderThickness="1"
CornerRadius="8">
<ItemsControl ItemsSource="{Binding PaidMessageSegments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:FileNameFormatSegmentViewModel">
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
FontFamily="Consolas"
TextWrapping="Wrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<TextBlock IsVisible="{Binding HasUnknownPaidMessageVariables}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding UnknownPaidMessageVariablesMessage}"
TextWrapping="Wrap" />
<TextBlock IsVisible="{Binding HasPaidMessageFileNameFormatError}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding PaidMessageFileNameFormatError}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel Spacing="8">
<TextBlock FontWeight="SemiBold" Foreground="{DynamicResource TextPrimaryBrush}" Text="Message Filename Format" />
<TextBox Text="{Binding MessageFileNameFormat}"
Watermark="Optional: override global message format" />
<Grid ClipToBounds="True">
<TextBox x:Name="MessageFileNameFormatTextBox"
Classes="fileNameOverlayInput"
Text="{Binding MessageFileNameFormat}"
Watermark="Optional: override global message format"
Padding="8,6"
CaretBrush="{DynamicResource TextPrimaryBrush}" />
<controls:FileNameFormatOverlayTextBlock IsHitTestVisible="False"
Margin="{Binding #MessageFileNameFormatTextBox.Padding}"
VerticalAlignment="Center"
Segments="{Binding MessageSegments}"
SourceTextBox="{Binding #MessageFileNameFormatTextBox}"
FontFamily="{Binding #MessageFileNameFormatTextBox.FontFamily}"
FontSize="{Binding #MessageFileNameFormatTextBox.FontSize}"
FontWeight="{Binding #MessageFileNameFormatTextBox.FontWeight}"
FontStyle="{Binding #MessageFileNameFormatTextBox.FontStyle}" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<ComboBox Grid.Column="0"
HorizontalAlignment="Stretch"
@ -1131,31 +1139,14 @@
Content="Insert"
Command="{Binding InsertMessageVariableCommand}" />
</Grid>
<Border Padding="8"
Background="{DynamicResource PreviewBackgroundBrush}"
BorderBrush="{DynamicResource PreviewBorderBrush}"
BorderThickness="1"
CornerRadius="8">
<ItemsControl ItemsSource="{Binding MessageSegments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:FileNameFormatSegmentViewModel">
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
FontFamily="Consolas"
TextWrapping="Wrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<TextBlock IsVisible="{Binding HasUnknownMessageVariables}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding UnknownMessageVariablesMessage}"
TextWrapping="Wrap" />
<TextBlock IsVisible="{Binding HasMessageFileNameFormatError}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding MessageFileNameFormatError}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">