Skip to content

Commit

Permalink
Workshop - Refactor markdown editor
Browse files Browse the repository at this point in the history
Workshop - Add changelog during upload
  • Loading branch information
RobertBeekman committed Apr 13, 2024
1 parent 62057d6 commit 7b71ee0
Show file tree
Hide file tree
Showing 24 changed files with 334 additions and 334 deletions.
51 changes: 51 additions & 0 deletions src/Artemis.UI/Controls/SplitMarkdownEditor.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
xmlns:fa="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Controls.SplitMarkdownEditor">
<Grid RowDefinitions="Auto,*">
<Grid Row="0" ColumnDefinitions="Auto,*">
<Label Grid.Column="0" Name="DescriptionEditorLabel" Target="DescriptionEditor" Margin="0 28 0 0" />
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<CheckBox Name="SynchronizedScrolling" IsChecked="True" VerticalAlignment="Bottom">Synchronized scrolling</CheckBox>
<fa:HyperlinkButton
Margin="0 0 0 -20"
Content="Markdown supported"
NavigateUri="https://wiki.artemis-rgb.com/guides/user/markdown?mtm_campaign=artemis&amp;mtm_kwd=markdown-editor"
HorizontalAlignment="Right" />
</StackPanel>
</Grid>

<Grid Grid.Row="1" Grid.Column="0" ColumnDefinitions="*,Auto,*">
<Border Grid.Column="0" BorderThickness="1"
BorderBrush="{DynamicResource TextControlBorderBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Background="{DynamicResource TextControlBackground}"
Padding="{DynamicResource TextControlThemePadding}">
<avaloniaEdit:TextEditor
FontFamily="{StaticResource RobotoMono}"
FontSize="13"
Name="DescriptionEditor"
TextChanged="DescriptionEditor_OnTextChanged"
WordWrap="True" />
</Border>

<GridSplitter Grid.Column="1" Margin="5 0"></GridSplitter>
<Border Grid.Column="2" Classes="card-condensed">
<mdxaml:MarkdownScrollViewer Margin="5 0"
Name="DescriptionPreview"
Markdown="{CompiledBinding Document.Text, Mode=OneWay, ElementName=DescriptionEditor}"
MarkdownStyleName="FluentAvalonia"
SaveScrollValueWhenContentUpdated="True">
<mdxaml:MarkdownScrollViewer.Styles>
<StyleInclude Source="/Styles/Markdown.axaml" />
</mdxaml:MarkdownScrollViewer.Styles>
</mdxaml:MarkdownScrollViewer>
</Border>
</Grid>
</Grid>
</UserControl>
150 changes: 150 additions & 0 deletions src/Artemis.UI/Controls/SplitMarkdownEditor.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Threading;
using AvaloniaEdit.TextMate;
using TextMateSharp.Grammars;
using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions;

namespace Artemis.UI.Controls;

public partial class SplitMarkdownEditor : UserControl
{
public static readonly StyledProperty<string> TitleProperty = AvaloniaProperty.Register<SplitMarkdownEditor, string>(nameof(Title), string.Empty);
public static readonly StyledProperty<string> MarkdownProperty = AvaloniaProperty.Register<SplitMarkdownEditor, string>(nameof(Markdown), string.Empty, defaultBindingMode: BindingMode.TwoWay);

private ScrollViewer? _editorScrollViewer;
private ScrollViewer? _previewScrollViewer;
private bool _scrolling;
private bool _updating;

public string Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}

public string Markdown
{
get => GetValue(MarkdownProperty);
set => SetValue(MarkdownProperty, value);
}

public SplitMarkdownEditor()
{
InitializeComponent();
PropertyChanged += OnPropertyChanged;

DescriptionEditorLabel.Content = Title;
DescriptionEditor.Options.AllowScrollBelowDocument = false;
}

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
if (this.TryFindResource("SystemAccentColorLight3", out object? resource) && resource is Color color)
DescriptionEditor.TextArea.TextView.LinkTextForegroundBrush = new ImmutableSolidColorBrush(color);

SetupScrollSync();

Dispatcher.UIThread.InvokeAsync(async () =>
{
// Installing is slow, wait for UI to settle
await Task.Delay(300);
RegistryOptions options = new(ThemeName.Dark);
TextMate.Installation? install = DescriptionEditor.InstallTextMate(options);
install.SetGrammar(options.GetScopeByExtension(".md"));
}, DispatcherPriority.ApplicationIdle);
}

private void SetupScrollSync()
{
if (_editorScrollViewer != null)
_editorScrollViewer.PropertyChanged -= EditorScrollViewerOnPropertyChanged;
if (_previewScrollViewer != null)
_previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged;

_editorScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionEditor).FirstOrDefault();
_previewScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionPreview).FirstOrDefault();

if (_editorScrollViewer != null)
_editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged;
if (_previewScrollViewer != null)
_previewScrollViewer.PropertyChanged += PreviewScrollViewerOnPropertyChanged;
}

private void EditorScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.Name != nameof(ScrollViewer.Offset) || _scrolling || SynchronizedScrolling.IsChecked != true)
return;

try
{
_scrolling = true;
SynchronizeScrollViewers(_editorScrollViewer, _previewScrollViewer);
}
finally
{
_scrolling = false;
}
}

private void PreviewScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.Name != nameof(ScrollViewer.Offset) || _scrolling || SynchronizedScrolling.IsChecked != true)
return;

try
{
_scrolling = true;
SynchronizeScrollViewers(_previewScrollViewer, _editorScrollViewer);
}
finally
{
_scrolling = false;
}
}

private void SynchronizeScrollViewers(ScrollViewer? source, ScrollViewer? target)
{
if (source == null || target == null)
return;

double sourceScrollableHeight = source.Extent.Height - source.Viewport.Height;
double targetScrollableHeight = target.Extent.Height - target.Viewport.Height;

if (sourceScrollableHeight != 0)
target.Offset = new Vector(target.Offset.X, targetScrollableHeight * (source.Offset.Y / sourceScrollableHeight));
}

private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == TitleProperty)
DescriptionEditorLabel.Content = Title;
else if (e.Property == MarkdownProperty && DescriptionEditor.Text != Markdown)
{
try
{
_updating = true;
DescriptionEditor.Clear();
DescriptionEditor.AppendText(Markdown);
}
finally
{
_updating = false;
}

}
}

private void DescriptionEditor_OnTextChanged(object? sender, EventArgs e)
{
if (!_updating && Markdown != DescriptionEditor.Text)
Markdown = DescriptionEditor.Text;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@
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:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput;assembly=Artemis.UI.Shared"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:categories="clr-namespace:Artemis.UI.Screens.Workshop.Categories"
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
xmlns:controls="clr-namespace:Artemis.UI.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsView"
x:DataType="details:EntrySpecificationsViewModel">
<Grid RowDefinitions="Auto,Auto,*,Auto">
<Grid RowDefinitions="Auto,*,Auto">
<StackPanel>
<StackPanel.Styles>
<Styles>
Expand Down Expand Up @@ -95,48 +92,9 @@
<tagsInput:TagsInput Tags="{CompiledBinding Tags}" />
</StackPanel>

<Grid Row="1" ColumnDefinitions="Auto,*">
<Label Grid.Column="0" Target="DescriptionEditor" Margin="0 28 0 0">Description</Label>

<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<CheckBox Name="SynchronizedScrolling" IsChecked="True" VerticalAlignment="Bottom">Synchronized scrolling</CheckBox>
<controls:HyperlinkButton
Margin="0 0 0 -20"
Content="Markdown supported"
NavigateUri="https://wiki.artemis-rgb.com/guides/user/markdown?mtm_campaign=artemis&amp;mtm_kwd=markdown-editor"
HorizontalAlignment="Right"/>
</StackPanel>
</Grid>

<Grid Grid.Row="2" ColumnDefinitions="*,Auto,*">
<Border Grid.Column="0" BorderThickness="1"
BorderBrush="{DynamicResource TextControlBorderBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Background="{DynamicResource TextControlBackground}"
Padding="{DynamicResource TextControlThemePadding}">
<avaloniaEdit:TextEditor
FontFamily="{StaticResource RobotoMono}"
FontSize="13"
Name="DescriptionEditor"
Document="{CompiledBinding MarkdownDocument}"
WordWrap="True" />
</Border>

<GridSplitter Grid.Column="1" Margin="5 0"></GridSplitter>
<Border Grid.Column="2" Classes="card-condensed">
<mdxaml:MarkdownScrollViewer Margin="5 0"
Name="DescriptionPreview"
Markdown="{CompiledBinding Description}"
MarkdownStyleName="FluentAvalonia"
SaveScrollValueWhenContentUpdated="True">
<mdxaml:MarkdownScrollViewer.Styles>
<StyleInclude Source="/Styles/Markdown.axaml" />
</mdxaml:MarkdownScrollViewer.Styles>
</mdxaml:MarkdownScrollViewer>
</Border>
</Grid>
<controls:SplitMarkdownEditor Grid.Row="1" Title="Description" Markdown="{CompiledBinding Description}"/>

<TextBlock Grid.Row="3"
<TextBlock Grid.Row="2"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
Margin="2 8 0 0"
IsVisible="{CompiledBinding !DescriptionValid}">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,100 +1,11 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.ReactiveUI;
using AvaloniaEdit.TextMate;
using ReactiveUI;
using TextMateSharp.Grammars;
using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions;

namespace Artemis.UI.Screens.Workshop.Entries.Details;

public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecificationsViewModel>
{
private ScrollViewer? _editorScrollViewer;
private ScrollViewer? _previewScrollViewer;
private bool _updating;

public EntrySpecificationsView()
{
InitializeComponent();

DescriptionEditor.Options.AllowScrollBelowDocument = false;
RegistryOptions options = new(ThemeName.Dark);
TextMate.Installation? install = TextMate.InstallTextMate(DescriptionEditor, options);

install.SetGrammar(options.GetScopeByExtension(".md"));

this.WhenActivated(_ => SetupScrollSync());
}

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
if (this.TryFindResource("SystemAccentColorLight3", out object? resource) && resource is Color color)
DescriptionEditor.TextArea.TextView.LinkTextForegroundBrush = new ImmutableSolidColorBrush(color);

base.OnAttachedToVisualTree(e);
}

private void SetupScrollSync()
{
if (_editorScrollViewer != null)
_editorScrollViewer.PropertyChanged -= EditorScrollViewerOnPropertyChanged;
if (_previewScrollViewer != null)
_previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged;

_editorScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionEditor).FirstOrDefault();
_previewScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionPreview).FirstOrDefault();

if (_editorScrollViewer != null)
_editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged;
if (_previewScrollViewer != null)
_previewScrollViewer.PropertyChanged += PreviewScrollViewerOnPropertyChanged;
}

private void EditorScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.Name != nameof(ScrollViewer.Offset) || _updating || SynchronizedScrolling.IsChecked != true)
return;

try
{
_updating = true;
SynchronizeScrollViewers(_editorScrollViewer, _previewScrollViewer);
}
finally
{
_updating = false;
}
}

private void PreviewScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.Name != nameof(ScrollViewer.Offset) || _updating || SynchronizedScrolling.IsChecked != true)
return;

try
{
_updating = true;
SynchronizeScrollViewers(_previewScrollViewer, _editorScrollViewer);
}
finally
{
_updating = false;
}
}

private void SynchronizeScrollViewers(ScrollViewer? source, ScrollViewer? target)
{
if (source == null || target == null)
return;

double sourceScrollableHeight = source.Extent.Height - source.Viewport.Height;
double targetScrollableHeight = target.Extent.Height - target.Viewport.Height;

if (sourceScrollableHeight != 0)
target.Offset = new Vector(target.Offset.X, targetScrollableHeight * (source.Offset.Y / sourceScrollableHeight));
}
}
Loading

0 comments on commit 7b71ee0

Please sign in to comment.