419 lines
14 KiB
C#
419 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
|
|
namespace Wino.Mail.Controls;
|
|
|
|
public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditorCommandControl
|
|
{
|
|
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
|
|
nameof(CommandTarget),
|
|
typeof(IEditorCommandTarget),
|
|
typeof(EditorTabbedCommandBarControl),
|
|
new PropertyMetadata(null, OnCommandTargetChanged));
|
|
|
|
public static readonly DependencyProperty PaneCustomContentProperty = DependencyProperty.Register(
|
|
nameof(PaneCustomContent),
|
|
typeof(object),
|
|
typeof(EditorTabbedCommandBarControl),
|
|
new PropertyMetadata(null));
|
|
|
|
public static readonly DependencyProperty InsertCustomContentProperty = DependencyProperty.Register(
|
|
nameof(InsertCustomContent),
|
|
typeof(object),
|
|
typeof(EditorTabbedCommandBarControl),
|
|
new PropertyMetadata(null));
|
|
|
|
public static readonly DependencyProperty OptionsCustomContentProperty = DependencyProperty.Register(
|
|
nameof(OptionsCustomContent),
|
|
typeof(object),
|
|
typeof(EditorTabbedCommandBarControl),
|
|
new PropertyMetadata(null));
|
|
|
|
private bool _isApplyingState;
|
|
private IEditorCommandTarget? _subscribedTarget;
|
|
|
|
public IEditorCommandTarget? CommandTarget
|
|
{
|
|
get => (IEditorCommandTarget?)GetValue(CommandTargetProperty);
|
|
set => SetValue(CommandTargetProperty, value);
|
|
}
|
|
|
|
public object? PaneCustomContent
|
|
{
|
|
get => GetValue(PaneCustomContentProperty);
|
|
set => SetValue(PaneCustomContentProperty, value);
|
|
}
|
|
|
|
public object? InsertCustomContent
|
|
{
|
|
get => GetValue(InsertCustomContentProperty);
|
|
set => SetValue(InsertCustomContentProperty, value);
|
|
}
|
|
|
|
public object? OptionsCustomContent
|
|
{
|
|
get => GetValue(OptionsCustomContentProperty);
|
|
set => SetValue(OptionsCustomContentProperty, value);
|
|
}
|
|
|
|
public EditorTabbedCommandBarControl()
|
|
{
|
|
InitializeComponent();
|
|
|
|
Loaded += OnLoaded;
|
|
Unloaded += OnUnloaded;
|
|
}
|
|
|
|
public void AttachCommandTarget(IEditorCommandTarget? target)
|
|
{
|
|
if (_subscribedTarget == target)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_subscribedTarget != null)
|
|
{
|
|
_subscribedTarget.StateChanged -= CommandTarget_StateChanged;
|
|
_subscribedTarget.CapabilitiesChanged -= CommandTarget_CapabilitiesChanged;
|
|
}
|
|
|
|
_subscribedTarget = target;
|
|
|
|
if (_subscribedTarget != null)
|
|
{
|
|
_subscribedTarget.StateChanged += CommandTarget_StateChanged;
|
|
_subscribedTarget.CapabilitiesChanged += CommandTarget_CapabilitiesChanged;
|
|
ApplyCapabilities(_subscribedTarget.Capabilities);
|
|
ApplyState(_subscribedTarget.CurrentState);
|
|
}
|
|
}
|
|
|
|
public void DetachCommandTarget()
|
|
{
|
|
if (_subscribedTarget == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_subscribedTarget.StateChanged -= CommandTarget_StateChanged;
|
|
_subscribedTarget.CapabilitiesChanged -= CommandTarget_CapabilitiesChanged;
|
|
_subscribedTarget = null;
|
|
}
|
|
|
|
private static void OnCommandTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var control = (EditorTabbedCommandBarControl)d;
|
|
control.AttachCommandTarget((IEditorCommandTarget?)e.NewValue);
|
|
}
|
|
|
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
|
{
|
|
AttachCommandTarget(CommandTarget);
|
|
}
|
|
|
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
|
{
|
|
DetachCommandTarget();
|
|
}
|
|
|
|
private void CommandTarget_StateChanged(object? sender, EditorState e)
|
|
{
|
|
ApplyState(e);
|
|
}
|
|
|
|
private void CommandTarget_CapabilitiesChanged(object? sender, EditorCapabilities e)
|
|
{
|
|
ApplyCapabilities(e);
|
|
}
|
|
|
|
private void ApplyCapabilities(EditorCapabilities capabilities)
|
|
{
|
|
FontFamilyComboBox.ItemsSource = capabilities.Fonts;
|
|
FontSizeComboBox.ItemsSource = capabilities.FontSizes;
|
|
AlignmentComboBox.ItemsSource = capabilities.Alignments;
|
|
ParagraphStyleComboBox.ItemsSource = capabilities.ParagraphStyles;
|
|
TextColorComboBox.ItemsSource = capabilities.TextColors;
|
|
HighlightColorComboBox.ItemsSource = capabilities.HighlightColors;
|
|
LineHeightComboBox.ItemsSource = capabilities.LineHeights;
|
|
}
|
|
|
|
private void ApplyState(EditorState state)
|
|
{
|
|
_isApplyingState = true;
|
|
|
|
BoldButton.IsChecked = state.IsBold;
|
|
ItalicButton.IsChecked = state.IsItalic;
|
|
UnderlineButton.IsChecked = state.IsUnderline;
|
|
StrikeButton.IsChecked = state.IsStrikethrough;
|
|
BulletListButton.IsChecked = state.IsUnorderedList;
|
|
OrderedListButton.IsChecked = state.IsOrderedList;
|
|
IndentButton.IsEnabled = state.CanIndent;
|
|
OutdentButton.IsEnabled = state.CanOutdent;
|
|
RemoveLinkButton.IsEnabled = !string.IsNullOrWhiteSpace(state.LinkUrl);
|
|
RemoveLinkButton.Visibility = RemoveLinkButton.IsEnabled ? Visibility.Visible : Visibility.Collapsed;
|
|
BuiltInToolbarButton.IsChecked = state.IsBuiltInToolbarVisible;
|
|
SpellCheckButton.IsChecked = state.IsSpellCheckEnabled;
|
|
|
|
AlignmentComboBox.SelectedItem = state.Alignment;
|
|
FontFamilyComboBox.SelectedItem = MatchStringItem(FontFamilyComboBox.ItemsSource, state.FontFamily);
|
|
FontSizeComboBox.SelectedItem = MatchValueItem<int>(FontSizeComboBox.ItemsSource, state.FontSize);
|
|
LineHeightComboBox.SelectedItem = MatchStringItem(LineHeightComboBox.ItemsSource, state.LineHeight);
|
|
ParagraphStyleComboBox.SelectedItem = MatchParagraphItem(state.ParagraphStyle);
|
|
TextColorComboBox.SelectedItem = MatchColorItem(TextColorComboBox.ItemsSource, state.TextColor);
|
|
HighlightColorComboBox.SelectedItem = MatchColorItem(HighlightColorComboBox.ItemsSource, state.HighlightColor);
|
|
|
|
_isApplyingState = false;
|
|
}
|
|
|
|
private static object? MatchStringItem(object? itemsSource, string? value)
|
|
{
|
|
if (itemsSource is IEnumerable<string> strings)
|
|
{
|
|
return strings.FirstOrDefault(item => string.Equals(item, value, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static object? MatchValueItem<T>(object? itemsSource, T? value) where T : struct
|
|
{
|
|
if (!value.HasValue || itemsSource is not IEnumerable<T> values)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (var item in values)
|
|
{
|
|
if (EqualityComparer<T>.Default.Equals(item, value.Value))
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private object? MatchParagraphItem(string? tag)
|
|
{
|
|
if (ParagraphStyleComboBox.ItemsSource is not IEnumerable<EditorParagraphStyleOption> styles)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return styles.FirstOrDefault(item => string.Equals(item.Tag, tag, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private static object? MatchColorItem(object? itemsSource, string? value)
|
|
{
|
|
if (itemsSource is not IEnumerable<EditorColorOption> colors)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return colors.FirstOrDefault(item => string.Equals(item.Value, value ?? string.Empty, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private async Task ExecuteAsync(EditorCommand command)
|
|
{
|
|
if (_isApplyingState || CommandTarget == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await CommandTarget.ExecuteCommandAsync(command);
|
|
}
|
|
|
|
private async void BoldButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleBold());
|
|
private async void ItalicButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleItalic());
|
|
private async void UnderlineButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleUnderline());
|
|
private async void StrikeButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleStrikethrough());
|
|
private async void BulletListButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleUnorderedList());
|
|
private async void OrderedListButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleOrderedList());
|
|
private async void IndentButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.Indent());
|
|
private async void OutdentButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.Outdent());
|
|
private async void ImageButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.InsertImage());
|
|
private async void EmojiButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.InsertEmoji());
|
|
private async void RemoveLinkButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.RemoveLink());
|
|
|
|
private async void AlignmentComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_isApplyingState || AlignmentComboBox.SelectedItem is not EditorTextAlignment alignment)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await ExecuteAsync(EditorCommand.SetAlignment(alignment));
|
|
}
|
|
|
|
private async void FontFamilyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_isApplyingState || FontFamilyComboBox.SelectedItem is not string fontFamily || string.IsNullOrWhiteSpace(fontFamily))
|
|
{
|
|
return;
|
|
}
|
|
|
|
await ExecuteAsync(EditorCommand.SetFontFamily(fontFamily));
|
|
}
|
|
|
|
private async void FontSizeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_isApplyingState || FontSizeComboBox.SelectedItem is not int fontSize)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await ExecuteAsync(EditorCommand.SetFontSize(fontSize));
|
|
}
|
|
|
|
private async void ParagraphStyleComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_isApplyingState || ParagraphStyleComboBox.SelectedItem is not EditorParagraphStyleOption paragraphStyle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await ExecuteAsync(EditorCommand.SetParagraphStyle(paragraphStyle.Tag));
|
|
}
|
|
|
|
private async void TextColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_isApplyingState || TextColorComboBox.SelectedItem is not EditorColorOption color)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await ExecuteAsync(EditorCommand.SetTextColor(color.Value));
|
|
}
|
|
|
|
private async void HighlightColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_isApplyingState || HighlightColorComboBox.SelectedItem is not EditorColorOption color)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await ExecuteAsync(EditorCommand.SetHighlightColor(color.Value));
|
|
}
|
|
|
|
private async void LineHeightComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (_isApplyingState || LineHeightComboBox.SelectedItem is not string lineHeight || string.IsNullOrWhiteSpace(lineHeight))
|
|
{
|
|
return;
|
|
}
|
|
|
|
await ExecuteAsync(EditorCommand.SetLineHeight(lineHeight));
|
|
}
|
|
|
|
private async void BuiltInToolbarButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
await ExecuteAsync(EditorCommand.ToggleBuiltInToolbar(BuiltInToolbarButton.IsChecked == true));
|
|
}
|
|
|
|
private async void SpellCheckButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
await ExecuteAsync(EditorCommand.ToggleSpellCheck(SpellCheckButton.IsChecked == true));
|
|
}
|
|
|
|
private async void LinkButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (CommandTarget == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var currentState = CommandTarget.CurrentState;
|
|
var urlTextBox = new TextBox
|
|
{
|
|
Header = "URL",
|
|
Text = currentState.LinkUrl ?? string.Empty,
|
|
PlaceholderText = "https://example.com"
|
|
};
|
|
var textTextBox = new TextBox
|
|
{
|
|
Header = "Text",
|
|
Text = currentState.SelectedText ?? string.Empty,
|
|
PlaceholderText = "Link text"
|
|
};
|
|
var openInNewWindow = new CheckBox
|
|
{
|
|
Content = "Open in new window",
|
|
IsChecked = true
|
|
};
|
|
|
|
var dialog = new ContentDialog
|
|
{
|
|
XamlRoot = XamlRoot,
|
|
Title = "Insert link",
|
|
PrimaryButtonText = "Apply",
|
|
CloseButtonText = "Cancel",
|
|
DefaultButton = ContentDialogButton.Primary,
|
|
Content = new StackPanel
|
|
{
|
|
Spacing = 12,
|
|
Children =
|
|
{
|
|
urlTextBox,
|
|
textTextBox,
|
|
openInNewWindow
|
|
}
|
|
}
|
|
};
|
|
|
|
if (await dialog.ShowAsync() == ContentDialogResult.Primary && !string.IsNullOrWhiteSpace(urlTextBox.Text))
|
|
{
|
|
await ExecuteAsync(EditorCommand.InsertLink(new EditorLinkCommandArgs(urlTextBox.Text.Trim(), textTextBox.Text.Trim(), openInNewWindow.IsChecked == true)));
|
|
}
|
|
}
|
|
|
|
private async void TableButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var rowsBox = new NumberBox
|
|
{
|
|
Header = "Rows",
|
|
Minimum = 1,
|
|
Maximum = 10,
|
|
SmallChange = 1,
|
|
Value = 2
|
|
};
|
|
var columnsBox = new NumberBox
|
|
{
|
|
Header = "Columns",
|
|
Minimum = 1,
|
|
Maximum = 10,
|
|
SmallChange = 1,
|
|
Value = 2
|
|
};
|
|
|
|
var dialog = new ContentDialog
|
|
{
|
|
XamlRoot = XamlRoot,
|
|
Title = "Insert table",
|
|
PrimaryButtonText = "Insert",
|
|
CloseButtonText = "Cancel",
|
|
DefaultButton = ContentDialogButton.Primary,
|
|
Content = new StackPanel
|
|
{
|
|
Spacing = 12,
|
|
Children =
|
|
{
|
|
rowsBox,
|
|
columnsBox
|
|
}
|
|
}
|
|
};
|
|
|
|
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
|
|
{
|
|
await ExecuteAsync(EditorCommand.InsertTable(new EditorTableCommandArgs((int)Math.Max(1, rowsBox.Value), (int)Math.Max(1, columnsBox.Value))));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|