mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-22 09:23:53 +08:00
feat(BorderBeam, Splash): update layout and introduce version tags in showcase components
This commit is contained in:
+6
-3
@@ -1233,7 +1233,7 @@ namespace AtomUI.Theme.Language
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.BasicDescription] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.BasicDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.BasicTitle] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.BasicTitle;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentCategory] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.ComponentCategory;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentStatusPreview] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.ComponentStatusPreview;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentIntroducedVersion] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.ComponentIntroducedVersion;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorCardDescription] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.CustomizedColorCardDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorDescription] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.CustomizedColorDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorTitle] = global::AtomUIGallery.ShowCases.BorderBeam.en_US.CustomizedColorTitle;
|
||||
@@ -1299,7 +1299,7 @@ namespace AtomUI.Theme.Language
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.BasicDescription] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.BasicDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.BasicTitle] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.BasicTitle;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentCategory] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.ComponentCategory;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentStatusPreview] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.ComponentStatusPreview;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentIntroducedVersion] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.ComponentIntroducedVersion;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorCardDescription] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.CustomizedColorCardDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorDescription] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.CustomizedColorDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorTitle] = global::AtomUIGallery.ShowCases.BorderBeam.zh_CN.CustomizedColorTitle;
|
||||
@@ -1365,7 +1365,7 @@ namespace AtomUI.Theme.Language
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.BasicDescription] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.BasicDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.BasicTitle] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.BasicTitle;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentCategory] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.ComponentCategory;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentStatusPreview] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.ComponentStatusPreview;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.ComponentIntroducedVersion] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.ComponentIntroducedVersion;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorCardDescription] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.CustomizedColorCardDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorDescription] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.CustomizedColorDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.BorderBeamShowCaseLangResourceKind.CustomizedColorTitle] = global::AtomUIGallery.ShowCases.BorderBeam.zh_TW.CustomizedColorTitle;
|
||||
@@ -18568,6 +18568,7 @@ namespace AtomUI.Theme.Language
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.BasicDescription] = global::AtomUIGallery.ShowCases.Splash.en_US.BasicDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.BasicTitle] = global::AtomUIGallery.ShowCases.Splash.en_US.BasicTitle;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentCategory] = global::AtomUIGallery.ShowCases.Splash.en_US.ComponentCategory;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentIntroducedVersion] = global::AtomUIGallery.ShowCases.Splash.en_US.ComponentIntroducedVersion;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentStatusPreview] = global::AtomUIGallery.ShowCases.Splash.en_US.ComponentStatusPreview;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComposedDescription] = global::AtomUIGallery.ShowCases.Splash.en_US.ComposedDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComposedTitle] = global::AtomUIGallery.ShowCases.Splash.en_US.ComposedTitle;
|
||||
@@ -18681,6 +18682,7 @@ namespace AtomUI.Theme.Language
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.BasicDescription] = global::AtomUIGallery.ShowCases.Splash.zh_CN.BasicDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.BasicTitle] = global::AtomUIGallery.ShowCases.Splash.zh_CN.BasicTitle;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentCategory] = global::AtomUIGallery.ShowCases.Splash.zh_CN.ComponentCategory;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentIntroducedVersion] = global::AtomUIGallery.ShowCases.Splash.zh_CN.ComponentIntroducedVersion;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentStatusPreview] = global::AtomUIGallery.ShowCases.Splash.zh_CN.ComponentStatusPreview;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComposedDescription] = global::AtomUIGallery.ShowCases.Splash.zh_CN.ComposedDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComposedTitle] = global::AtomUIGallery.ShowCases.Splash.zh_CN.ComposedTitle;
|
||||
@@ -18794,6 +18796,7 @@ namespace AtomUI.Theme.Language
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.BasicDescription] = global::AtomUIGallery.ShowCases.Splash.zh_TW.BasicDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.BasicTitle] = global::AtomUIGallery.ShowCases.Splash.zh_TW.BasicTitle;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentCategory] = global::AtomUIGallery.ShowCases.Splash.zh_TW.ComponentCategory;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentIntroducedVersion] = global::AtomUIGallery.ShowCases.Splash.zh_TW.ComponentIntroducedVersion;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComponentStatusPreview] = global::AtomUIGallery.ShowCases.Splash.zh_TW.ComponentStatusPreview;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComposedDescription] = global::AtomUIGallery.ShowCases.Splash.zh_TW.ComposedDescription;
|
||||
dictionary[global::AtomUIGallery.Localization.SplashShowCaseLangResourceKind.ComposedTitle] = global::AtomUIGallery.ShowCases.Splash.zh_TW.ComposedTitle;
|
||||
|
||||
+2
-1
@@ -387,7 +387,7 @@ namespace AtomUIGallery.Localization
|
||||
BasicDescription,
|
||||
BasicTitle,
|
||||
ComponentCategory,
|
||||
ComponentStatusPreview,
|
||||
ComponentIntroducedVersion,
|
||||
CustomizedColorCardDescription,
|
||||
CustomizedColorDescription,
|
||||
CustomizedColorTitle,
|
||||
@@ -5841,6 +5841,7 @@ namespace AtomUIGallery.Localization
|
||||
BasicDescription,
|
||||
BasicTitle,
|
||||
ComponentCategory,
|
||||
ComponentIntroducedVersion,
|
||||
ComponentStatusPreview,
|
||||
ComposedDescription,
|
||||
ComposedTitle,
|
||||
|
||||
@@ -10,7 +10,7 @@ internal partial class en_US : LanguageProvider
|
||||
public const string ScenarioApi = "API";
|
||||
public const string ScenarioDesignToken = "Design Token";
|
||||
public const string ComponentCategory = "Other";
|
||||
public const string ComponentStatusPreview = "v6.0.5";
|
||||
public const string ComponentIntroducedVersion = "v6.0.5";
|
||||
public const string PageSubtitle = "Draw an animated highlight along a container boundary.";
|
||||
public const string PageDescription = "BorderBeam wraps a content control and renders a non-interactive beam on its border. It is decorative, follows theme motion settings, and can use a solid color or gradient stops.";
|
||||
public const string InfoNamespaceLabel = "Namespace:";
|
||||
|
||||
@@ -10,7 +10,7 @@ internal partial class zh_CN : LanguageProvider
|
||||
public const string ScenarioApi = "API";
|
||||
public const string ScenarioDesignToken = "设计变量";
|
||||
public const string ComponentCategory = "其他";
|
||||
public const string ComponentStatusPreview = "v6.0.5";
|
||||
public const string ComponentIntroducedVersion = "v6.0.5";
|
||||
public const string PageSubtitle = "沿容器边界绘制动态高光。";
|
||||
public const string PageDescription = "BorderBeam 包裹一个内容控件,并在其边界上渲染不参与交互的流光层。它只承担装饰性强调,遵守主题动效设置,并支持单色或渐变停靠点。";
|
||||
public const string InfoNamespaceLabel = "命名空间:";
|
||||
|
||||
@@ -10,7 +10,7 @@ internal partial class zh_TW : LanguageProvider
|
||||
public const string ScenarioApi = "API";
|
||||
public const string ScenarioDesignToken = "設計變數";
|
||||
public const string ComponentCategory = "其他";
|
||||
public const string ComponentStatusPreview = "v6.0.5";
|
||||
public const string ComponentIntroducedVersion = "v6.0.5";
|
||||
public const string PageSubtitle = "沿容器邊界繪製動態高光。";
|
||||
public const string PageDescription = "BorderBeam 包裹一個內容控制項,並在其邊界上渲染不參與互動的流光層。它只承擔裝飾性強調,遵守主題動效設定,並支援單色或漸變停靠點。";
|
||||
public const string InfoNamespaceLabel = "命名空間:";
|
||||
|
||||
+18
-11
@@ -11,20 +11,27 @@
|
||||
<StackPanel Margin="28,24,28,18"
|
||||
Spacing="16">
|
||||
<StackPanel Spacing="6">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
VerticalAlignment="Center">
|
||||
<atom:TextBlock Text="BorderBeam"
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
VerticalAlignment="Center">
|
||||
<atom:TextBlock Grid.Column="0"
|
||||
Text="BorderBeam"
|
||||
FontSize="26"
|
||||
FontWeight="Bold"
|
||||
VerticalAlignment="Center" />
|
||||
<atom:Tag Text="{gallery:BorderBeamShowCaseLangResource ComponentCategory}"
|
||||
TagColor="geekblue"
|
||||
VerticalAlignment="Center" />
|
||||
<atom:Tag Text="{gallery:BorderBeamShowCaseLangResource ComponentStatusPreview}"
|
||||
TagColor="processing"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<WrapPanel Grid.Column="1"
|
||||
Margin="10,0,0,0"
|
||||
ItemSpacing="10"
|
||||
LineSpacing="6"
|
||||
VerticalAlignment="Center">
|
||||
<atom:Tag Text="{gallery:BorderBeamShowCaseLangResource ComponentCategory}"
|
||||
TagColor="blue"
|
||||
VerticalAlignment="Center" />
|
||||
<atom:Tag Text="{gallery:BorderBeamShowCaseLangResource ComponentIntroducedVersion}"
|
||||
TagColor="blue"
|
||||
IsBordered="False"
|
||||
VerticalAlignment="Center" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
<atom:TextBlock Text="{gallery:BorderBeamShowCaseLangResource PageSubtitle}"
|
||||
FontSize="15"
|
||||
Foreground="{atom:SharedTokenResource ColorTextSecondary}" />
|
||||
|
||||
@@ -11,6 +11,7 @@ internal partial class en_US : LanguageProvider
|
||||
public const string ScenarioDesignToken = "Design Token";
|
||||
public const string ComponentCategory = "Other";
|
||||
public const string ComponentStatusPreview = "Preview";
|
||||
public const string ComponentIntroducedVersion = "v6.0.7";
|
||||
public const string PageSubtitle = "Present desktop startup progress before the main workspace is ready.";
|
||||
public const string PageDescription = "Splash combines a compact branded surface, loading indicator, determinate progress, status copy, and optional footer. The service API can host it in a borderless startup window, while the visual control can be previewed directly in Gallery.";
|
||||
public const string InfoNamespaceLabel = "Namespace:";
|
||||
|
||||
@@ -11,6 +11,7 @@ internal partial class zh_CN : LanguageProvider
|
||||
public const string ScenarioDesignToken = "Design Token";
|
||||
public const string ComponentCategory = "其他";
|
||||
public const string ComponentStatusPreview = "预览";
|
||||
public const string ComponentIntroducedVersion = "v6.0.7";
|
||||
public const string PageSubtitle = "在主工作区准备完成前呈现桌面启动进度。";
|
||||
public const string PageDescription = "Splash 组合紧凑的品牌面板、加载指示、确定进度、状态文案和可选页脚。服务 API 可将它托管在无边框启动窗口中,视觉控件也可以直接在 Gallery 中预览。";
|
||||
public const string InfoNamespaceLabel = "命名空间:";
|
||||
|
||||
@@ -11,6 +11,7 @@ internal partial class zh_TW : LanguageProvider
|
||||
public const string ScenarioDesignToken = "Design Token";
|
||||
public const string ComponentCategory = "其他";
|
||||
public const string ComponentStatusPreview = "預覽";
|
||||
public const string ComponentIntroducedVersion = "v6.0.7";
|
||||
public const string PageSubtitle = "在主工作區準備完成前呈現桌面啟動進度。";
|
||||
public const string PageDescription = "Splash 組合緊湊的品牌面板、載入指示、確定進度、狀態文案和可選頁腳。服務 API 可將它託管在無邊框啟動視窗中,視覺控件也可以直接在 Gallery 中預覽。";
|
||||
public const string InfoNamespaceLabel = "命名空間:";
|
||||
|
||||
@@ -11,20 +11,30 @@
|
||||
<StackPanel Margin="28,24,28,18"
|
||||
Spacing="16">
|
||||
<StackPanel Spacing="6">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
VerticalAlignment="Center">
|
||||
<atom:TextBlock Text="Splash"
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
VerticalAlignment="Center">
|
||||
<atom:TextBlock Grid.Column="0"
|
||||
Text="Splash"
|
||||
FontSize="26"
|
||||
FontWeight="Bold"
|
||||
VerticalAlignment="Center" />
|
||||
<atom:Tag Text="{gallery:SplashShowCaseLangResource ComponentCategory}"
|
||||
TagColor="geekblue"
|
||||
VerticalAlignment="Center" />
|
||||
<atom:Tag Text="{gallery:SplashShowCaseLangResource ComponentStatusPreview}"
|
||||
TagColor="processing"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<WrapPanel Grid.Column="1"
|
||||
Margin="10,0,0,0"
|
||||
ItemSpacing="10"
|
||||
LineSpacing="6"
|
||||
VerticalAlignment="Center">
|
||||
<atom:Tag Text="{gallery:SplashShowCaseLangResource ComponentCategory}"
|
||||
TagColor="blue"
|
||||
VerticalAlignment="Center" />
|
||||
<atom:Tag Text="{gallery:SplashShowCaseLangResource ComponentStatusPreview}"
|
||||
TagColor="processing"
|
||||
VerticalAlignment="Center" />
|
||||
<atom:Tag Text="{gallery:SplashShowCaseLangResource ComponentIntroducedVersion}"
|
||||
TagColor="blue"
|
||||
IsBordered="False"
|
||||
VerticalAlignment="Center" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
<atom:TextBlock Text="{gallery:SplashShowCaseLangResource PageSubtitle}"
|
||||
FontSize="15"
|
||||
Foreground="{atom:SharedTokenResource ColorTextSecondary}" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Collections.ObjectModel;
|
||||
using AtomUI.Controls;
|
||||
using AtomUI.Theme;
|
||||
using Avalonia;
|
||||
@@ -8,10 +9,15 @@ using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Document;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using TextMateSharp.Model;
|
||||
using TextMateSharp.Grammars;
|
||||
using TextMateSharp.Registry;
|
||||
using TextMateSharp.Themes;
|
||||
using AtomUIContextMenu = AtomUI.Desktop.Controls.ContextMenu;
|
||||
using AtomUIMenuItem = AtomUI.Desktop.Controls.MenuItem;
|
||||
|
||||
@@ -42,13 +48,14 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
IsEnabled = false
|
||||
};
|
||||
|
||||
private TextMate.Installation? _textMateInstallation;
|
||||
private GalleryTextMateInstallation? _textMateInstallation;
|
||||
private RegistryOptions? _registryOptions;
|
||||
private ScrollBar? _horizontalScrollBar;
|
||||
private Application? _subscribedApplication;
|
||||
private IThemeManager? _subscribedThemeManager;
|
||||
private IDisposable? _themeVariantSubscription;
|
||||
private ThemeName? _currentSyntaxTheme;
|
||||
private bool _isHorizontalScrollBarInsetUpdatePending;
|
||||
private bool _isUpdatingScrollBarInset;
|
||||
private bool _isDisposed;
|
||||
|
||||
@@ -56,7 +63,6 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
{
|
||||
InitializeComponent();
|
||||
_editor = this.FindControl<TextEditor>("PART_Editor")!;
|
||||
_editor.LayoutUpdated += HandleEditorLayoutUpdated;
|
||||
_editor.TextArea.SelectionChanged += HandleEditorSelectionChanged;
|
||||
ActualThemeVariantChanged += HandleActualThemeVariantChanged;
|
||||
ConfigureEditorContextMenu();
|
||||
@@ -107,7 +113,7 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
_textMateInstallation = null;
|
||||
_registryOptions = null;
|
||||
ReleaseThemeVariantSubscriptions();
|
||||
_editor.LayoutUpdated -= HandleEditorLayoutUpdated;
|
||||
CancelHorizontalScrollBarGutterInsetUpdate();
|
||||
_editor.TextArea.SelectionChanged -= HandleEditorSelectionChanged;
|
||||
_editor.Document = null;
|
||||
_copyMenuItem.Click -= HandleCopyMenuItemClick;
|
||||
@@ -136,7 +142,7 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
else if (change.Property == ShowLineNumbersProperty)
|
||||
{
|
||||
_editor.ShowLineNumbers = ShowLineNumbers;
|
||||
UpdateHorizontalScrollBarGutterInset();
|
||||
RequestHorizontalScrollBarGutterInsetUpdate();
|
||||
}
|
||||
else if (change.Property == LightSyntaxThemeProperty ||
|
||||
change.Property == DarkSyntaxThemeProperty)
|
||||
@@ -150,16 +156,19 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
base.OnAttachedToVisualTree(e);
|
||||
SubscribeThemeVariantChanges();
|
||||
ApplySyntaxTheme();
|
||||
RequestHorizontalScrollBarGutterInsetUpdate();
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
CancelHorizontalScrollBarGutterInsetUpdate();
|
||||
ReleaseThemeVariantSubscriptions();
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
}
|
||||
|
||||
private void HandleEditorLayoutUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
CancelHorizontalScrollBarGutterInsetUpdate();
|
||||
UpdateHorizontalScrollBarGutterInset();
|
||||
}
|
||||
|
||||
@@ -201,12 +210,33 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
ApplySyntaxTheme();
|
||||
}
|
||||
|
||||
private void RequestHorizontalScrollBarGutterInsetUpdate()
|
||||
{
|
||||
if (_isDisposed || _isHorizontalScrollBarInsetUpdatePending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isHorizontalScrollBarInsetUpdatePending = true;
|
||||
_editor.LayoutUpdated += HandleEditorLayoutUpdated;
|
||||
}
|
||||
|
||||
private void CancelHorizontalScrollBarGutterInsetUpdate()
|
||||
{
|
||||
if (!_isHorizontalScrollBarInsetUpdatePending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isHorizontalScrollBarInsetUpdatePending = false;
|
||||
_editor.LayoutUpdated -= HandleEditorLayoutUpdated;
|
||||
}
|
||||
|
||||
private void UpdateHorizontalScrollBarGutterInset()
|
||||
{
|
||||
// This runs from LayoutUpdated. Mutating the scroll bar Margin below triggers another
|
||||
// layout pass, which re-enters here. Without this guard, dragging a selection past the
|
||||
// right edge (which auto-scrolls and fires LayoutUpdated continuously) spins the layout
|
||||
// system forever and hangs the UI thread.
|
||||
// Mutating the scroll bar Margin triggers another layout pass, so this method must only
|
||||
// run from the one-shot request path above. Drag selection auto-scroll produces repeated
|
||||
// LayoutUpdated ticks, and a permanent handler can spin the UI thread.
|
||||
if (_isUpdatingScrollBarInset)
|
||||
{
|
||||
return;
|
||||
@@ -277,6 +307,7 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
private void UpdateText()
|
||||
{
|
||||
_editor.Text = CodeText ?? string.Empty;
|
||||
RequestHorizontalScrollBarGutterInsetUpdate();
|
||||
}
|
||||
|
||||
private void ConfigureEditorContextMenu()
|
||||
@@ -290,7 +321,7 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
|
||||
private void UpdateCopyMenuItemState()
|
||||
{
|
||||
_copyMenuItem.IsEnabled = !string.IsNullOrEmpty(GetSelectedCodeText());
|
||||
_copyMenuItem.IsEnabled = !_editor.TextArea.Selection.IsEmpty;
|
||||
}
|
||||
|
||||
private string GetSelectedCodeText()
|
||||
@@ -331,7 +362,7 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
|
||||
var syntaxTheme = ResolveSyntaxTheme();
|
||||
_registryOptions = new RegistryOptions(syntaxTheme);
|
||||
_textMateInstallation = _editor.InstallTextMate(_registryOptions);
|
||||
_textMateInstallation = new GalleryTextMateInstallation(_editor, _registryOptions);
|
||||
_currentSyntaxTheme = syntaxTheme;
|
||||
}
|
||||
|
||||
@@ -443,4 +474,439 @@ public sealed partial class GalleryCodeViewer : UserControl, IDisposable
|
||||
|
||||
return _registryOptions?.GetScopeByExtension(extension);
|
||||
}
|
||||
|
||||
private sealed class GalleryTextMateInstallation : IDisposable
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Registry _textMateRegistry;
|
||||
private readonly TextEditor _editor;
|
||||
private readonly TextMateColoringTransformer _transformer;
|
||||
private readonly bool _ownsTransformer;
|
||||
private Action<Exception>? _exceptionHandler;
|
||||
private GalleryTextEditorModel? _editorModel;
|
||||
private IGrammar? _grammar;
|
||||
private TMModel? _tmModel;
|
||||
private ReadOnlyDictionary<string, string>? _themeColorsDictionary;
|
||||
private bool _isDisposed;
|
||||
|
||||
public GalleryTextMateInstallation(TextEditor editor,
|
||||
IRegistryOptions registryOptions,
|
||||
Action<Exception>? exceptionHandler = null)
|
||||
{
|
||||
RegistryOptions = registryOptions ?? throw new ArgumentNullException(nameof(registryOptions));
|
||||
_editor = editor ?? throw new ArgumentNullException(nameof(editor));
|
||||
_exceptionHandler = exceptionHandler;
|
||||
_textMateRegistry = new Registry(registryOptions);
|
||||
_transformer = _editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.FirstOrDefault() ??
|
||||
new TextMateColoringTransformer(_editor.TextArea.TextView, _exceptionHandler);
|
||||
|
||||
if (!_editor.TextArea.TextView.LineTransformers.Contains(_transformer))
|
||||
{
|
||||
_editor.TextArea.TextView.LineTransformers.Add(_transformer);
|
||||
_ownsTransformer = true;
|
||||
}
|
||||
|
||||
SetTheme(registryOptions.GetDefaultTheme());
|
||||
_editor.DocumentChanged += HandleEditorDocumentChanged;
|
||||
HandleEditorDocumentChanged(_editor, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public IRegistryOptions RegistryOptions { get; }
|
||||
|
||||
public void SetGrammar(string scopeName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
lock (_lock)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
SetGrammarInternal(_textMateRegistry.LoadGrammar(scopeName));
|
||||
ForceTokenizeDocument();
|
||||
}
|
||||
_editor.TextArea.TextView.Redraw();
|
||||
}
|
||||
|
||||
public bool TryGetThemeColor(string colorKey, out string? colorString)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return (_themeColorsDictionary ?? throw new ObjectDisposedException(nameof(GalleryTextMateInstallation)))
|
||||
.TryGetValue(colorKey, out colorString);
|
||||
}
|
||||
|
||||
public void SetTheme(IRawTheme theme)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
lock (_lock)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_textMateRegistry.SetTheme(theme);
|
||||
var textMateTheme = _textMateRegistry.GetTheme();
|
||||
_transformer.SetTheme(textMateTheme);
|
||||
_tmModel?.InvalidateLine(0);
|
||||
_editorModel?.InvalidateViewPortLines();
|
||||
_themeColorsDictionary = textMateTheme.GetGuiColorDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GalleryTextEditorModel? editorModel;
|
||||
TMModel? tmModel;
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
editorModel = _editorModel;
|
||||
_editorModel = null;
|
||||
tmModel = _tmModel;
|
||||
_tmModel = null;
|
||||
_grammar = null;
|
||||
_themeColorsDictionary = null;
|
||||
_exceptionHandler = null;
|
||||
}
|
||||
|
||||
_editor.DocumentChanged -= HandleEditorDocumentChanged;
|
||||
editorModel?.Dispose();
|
||||
DisposeTMModel(tmModel);
|
||||
|
||||
if (_ownsTransformer)
|
||||
{
|
||||
_editor.TextArea.TextView.LineTransformers.Remove(_transformer);
|
||||
_transformer.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
_transformer.SetModel(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEditorDocumentChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_isDisposed || _editor.Document is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed || _editor.Document is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_editorModel?.Dispose();
|
||||
DisposeTMModel(_tmModel);
|
||||
_editorModel = new GalleryTextEditorModel(
|
||||
_editor.TextArea.TextView,
|
||||
_editor.Document,
|
||||
_exceptionHandler);
|
||||
_tmModel = new TMModel(_editorModel);
|
||||
_tmModel.SetGrammar(_grammar);
|
||||
_transformer.SetModel(_editor.Document, _tmModel);
|
||||
_tmModel.AddModelTokensChangedListener(_transformer);
|
||||
ForceTokenizeDocument();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_exceptionHandler?.Invoke(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetGrammarInternal(IGrammar grammar)
|
||||
{
|
||||
_grammar = grammar;
|
||||
_transformer.SetGrammar(_grammar);
|
||||
}
|
||||
|
||||
private void ForceTokenizeDocument()
|
||||
{
|
||||
_editorModel?.ForceTokenizeAllLines();
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(GalleryTextMateInstallation));
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeTMModel(TMModel? tmModel)
|
||||
{
|
||||
if (tmModel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tmModel.RemoveModelTokensChangedListener(_transformer);
|
||||
tmModel.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class GalleryTextEditorModel : AbstractLineList, IDisposable
|
||||
{
|
||||
private readonly TextDocument _document;
|
||||
private readonly AvaloniaEdit.Rendering.TextView _textView;
|
||||
private readonly Action<Exception>? _exceptionHandler;
|
||||
private DocumentSnapshot _documentSnapshot;
|
||||
private InvalidLineRange? _invalidRange;
|
||||
private bool _isViewportTokenizationPending;
|
||||
private int _pendingViewportStartLine = -1;
|
||||
private int _pendingViewportEndLine = -1;
|
||||
private bool _isDisposed;
|
||||
|
||||
public GalleryTextEditorModel(AvaloniaEdit.Rendering.TextView textView,
|
||||
TextDocument document,
|
||||
Action<Exception>? exceptionHandler)
|
||||
{
|
||||
_textView = textView;
|
||||
_document = document;
|
||||
_exceptionHandler = exceptionHandler;
|
||||
_documentSnapshot = new DocumentSnapshot(_document);
|
||||
for (var i = 0; i < _document.LineCount; i++)
|
||||
{
|
||||
AddLine(i);
|
||||
}
|
||||
|
||||
_document.Changing += HandleDocumentChanging;
|
||||
_document.Changed += HandleDocumentChanged;
|
||||
_document.UpdateFinished += HandleDocumentUpdateFinished;
|
||||
_textView.ScrollOffsetChanged += HandleTextViewScrollOffsetChanged;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
_document.Changing -= HandleDocumentChanging;
|
||||
_document.Changed -= HandleDocumentChanged;
|
||||
_document.UpdateFinished -= HandleDocumentUpdateFinished;
|
||||
_textView.ScrollOffsetChanged -= HandleTextViewScrollOffsetChanged;
|
||||
}
|
||||
|
||||
public override void UpdateLine(int lineIndex)
|
||||
{
|
||||
}
|
||||
|
||||
public override int GetNumberOfLines()
|
||||
{
|
||||
return _documentSnapshot.LineCount;
|
||||
}
|
||||
|
||||
public override LineText GetLineTextIncludingTerminators(int lineIndex)
|
||||
{
|
||||
return _documentSnapshot.GetLineTextIncludingTerminatorAsMemory(lineIndex);
|
||||
}
|
||||
|
||||
public override int GetLineLength(int lineIndex)
|
||||
{
|
||||
return _documentSnapshot.GetLineLength(lineIndex);
|
||||
}
|
||||
|
||||
public void InvalidateViewPortLines()
|
||||
{
|
||||
if (_textView.VisualLinesValid && _textView.VisualLines.Count != 0)
|
||||
{
|
||||
InvalidateLineRange(GetFirstVisibleLineIndex(), GetLastVisibleLineIndex());
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceTokenizeAllLines()
|
||||
{
|
||||
var lineCount = _documentSnapshot.LineCount;
|
||||
if (lineCount > 0)
|
||||
{
|
||||
ForceTokenization(0, lineCount - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTextViewScrollOffsetChanged(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
RequestViewportTokenization();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_exceptionHandler?.Invoke(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestViewportTokenization()
|
||||
{
|
||||
if (_isDisposed || !_textView.VisualLinesValid || _textView.VisualLines.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var startLine = GetFirstVisibleLineIndex();
|
||||
var endLine = GetLastVisibleLineIndex();
|
||||
if (_pendingViewportStartLine < 0)
|
||||
{
|
||||
_pendingViewportStartLine = startLine;
|
||||
_pendingViewportEndLine = endLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
_pendingViewportStartLine = Math.Min(_pendingViewportStartLine, startLine);
|
||||
_pendingViewportEndLine = Math.Max(_pendingViewportEndLine, endLine);
|
||||
}
|
||||
|
||||
if (_isViewportTokenizationPending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isViewportTokenizationPending = true;
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(
|
||||
ProcessPendingViewportTokenization,
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void ProcessPendingViewportTokenization()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var startLine = _pendingViewportStartLine;
|
||||
var endLine = _pendingViewportEndLine;
|
||||
_pendingViewportStartLine = -1;
|
||||
_pendingViewportEndLine = -1;
|
||||
_isViewportTokenizationPending = false;
|
||||
if (startLine >= 0 && endLine >= startLine)
|
||||
{
|
||||
ForceTokenization(startLine, endLine);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDocumentChanging(object? sender, DocumentChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.RemovalLength <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var startLine = _document.GetLineByOffset(e.Offset).LineNumber - 1;
|
||||
var endLine = _document.GetLineByOffset(e.Offset + e.RemovalLength).LineNumber - 1;
|
||||
for (var line = endLine; line > startLine; line--)
|
||||
{
|
||||
RemoveLine(line);
|
||||
}
|
||||
_documentSnapshot.RemoveLines(startLine, endLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_exceptionHandler?.Invoke(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDocumentChanged(object? sender, DocumentChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var startLine = _document.GetLineByOffset(e.Offset).LineNumber - 1;
|
||||
var endLine = startLine;
|
||||
if (e.InsertionLength > 0)
|
||||
{
|
||||
endLine = _document.GetLineByOffset(e.Offset + e.InsertionLength).LineNumber - 1;
|
||||
for (var line = startLine; line < endLine; line++)
|
||||
{
|
||||
AddLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
_documentSnapshot.Update(e);
|
||||
SetInvalidRange(startLine == 0 ? startLine : startLine - 1, endLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_exceptionHandler?.Invoke(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetInvalidRange(int startLine, int endLine)
|
||||
{
|
||||
if (!_document.IsInUpdate)
|
||||
{
|
||||
InvalidateLineRange(startLine, endLine);
|
||||
}
|
||||
else if (_invalidRange is null)
|
||||
{
|
||||
_invalidRange = new InvalidLineRange(startLine, endLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
_invalidRange.SetInvalidRange(startLine, endLine);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDocumentUpdateFinished(object? sender, EventArgs e)
|
||||
{
|
||||
if (_invalidRange is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var startLine = Math.Clamp(_invalidRange.StartLine, 0, _documentSnapshot.LineCount - 1);
|
||||
var endLine = Math.Clamp(_invalidRange.EndLine, 0, _documentSnapshot.LineCount - 1);
|
||||
InvalidateLineRange(startLine, endLine);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_invalidRange = null;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetFirstVisibleLineIndex()
|
||||
{
|
||||
return _textView.VisualLines[0].FirstDocumentLine.LineNumber - 1;
|
||||
}
|
||||
|
||||
private int GetLastVisibleLineIndex()
|
||||
{
|
||||
return _textView.VisualLines[^1].LastDocumentLine.LineNumber - 1;
|
||||
}
|
||||
|
||||
private sealed class InvalidLineRange
|
||||
{
|
||||
public InvalidLineRange(int startLine, int endLine)
|
||||
{
|
||||
StartLine = startLine;
|
||||
EndLine = endLine;
|
||||
}
|
||||
|
||||
public int StartLine { get; private set; }
|
||||
|
||||
public int EndLine { get; private set; }
|
||||
|
||||
public void SetInvalidRange(int startLine, int endLine)
|
||||
{
|
||||
StartLine = Math.Min(StartLine, startLine);
|
||||
EndLine = Math.Max(EndLine, endLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+200
-1
@@ -8,6 +8,7 @@ using AtomUI.Toolkits.GalleryBase.SourceCode;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Headless;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
@@ -16,6 +17,7 @@ using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Editing;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using Shouldly;
|
||||
using TextMateSharp.Registry;
|
||||
using TextMateSharp.Grammars;
|
||||
@@ -133,6 +135,7 @@ public class GalleryCodeViewerRuntimeTests
|
||||
var horizontalScrollBar = editor.GetVisualDescendants()
|
||||
.OfType<ScrollBar>()
|
||||
.Single(scrollBar => scrollBar.Orientation == Orientation.Horizontal);
|
||||
var scrollViewer = GetEditorScrollViewer(editor);
|
||||
|
||||
// Drive the editor through a range of horizontal scroll offsets, forcing a layout
|
||||
// pass at each step. This reproduces the auto-scroll-while-selecting feedback loop:
|
||||
@@ -140,7 +143,7 @@ public class GalleryCodeViewerRuntimeTests
|
||||
// previously re-triggered layout forever and hung the UI thread).
|
||||
double MeasureInsetAfterScroll(double offset)
|
||||
{
|
||||
editor.ScrollToHorizontalOffset(offset);
|
||||
scrollViewer.Offset = new Vector(offset, scrollViewer.Offset.Y);
|
||||
editor.InvalidateMeasure();
|
||||
editor.InvalidateArrange();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
@@ -164,6 +167,192 @@ public class GalleryCodeViewerRuntimeTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GalleryCodeViewer_Selection_Drag_AutoScroll_Does_Not_Hang()
|
||||
{
|
||||
var viewer = new GalleryCodeViewer
|
||||
{
|
||||
CodeText = string.Join(
|
||||
"\n",
|
||||
Enumerable.Range(1, 40).Select(index => $"{index}: {new string('x', 260)}")),
|
||||
Language = "csharp"
|
||||
};
|
||||
var window = new Window
|
||||
{
|
||||
Width = 640,
|
||||
Height = 480,
|
||||
Content = viewer
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
window.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
viewer.ApplyTemplate();
|
||||
|
||||
var editor = viewer.GetVisualDescendants()
|
||||
.OfType<TextEditor>()
|
||||
.Single();
|
||||
editor.ApplyTemplate();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
var textView = editor.TextArea.TextView;
|
||||
var startPoint = textView.TranslatePoint(new Point(80, 22), window).ShouldNotBeNull();
|
||||
var dragPoint = textView.TranslatePoint(new Point(textView.Bounds.Width + 160, 82), window)
|
||||
.ShouldNotBeNull();
|
||||
|
||||
window.MouseMove(startPoint);
|
||||
window.MouseDown(startPoint, MouseButton.Left);
|
||||
window.MouseMove(dragPoint);
|
||||
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
}
|
||||
|
||||
window.MouseUp(dragPoint, MouseButton.Left);
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
editor.HorizontalOffset.ShouldBeGreaterThan(0);
|
||||
editor.TextArea.Selection.IsEmpty.ShouldBeFalse();
|
||||
editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.ShouldNotBeEmpty();
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
viewer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GalleryCodeViewer_Keeps_TextMate_When_Click_Does_Not_Start_Selection_Drag()
|
||||
{
|
||||
var viewer = new GalleryCodeViewer
|
||||
{
|
||||
CodeText = string.Join(
|
||||
"\n",
|
||||
Enumerable.Repeat(
|
||||
" <atom:TextBlock Foreground=\"{atom:SharedTokenResource ColorTextTertiary}\" Text=\"{Binding Description}\" />",
|
||||
40)),
|
||||
Language = "axaml"
|
||||
};
|
||||
var window = new Window
|
||||
{
|
||||
Width = 640,
|
||||
Height = 480,
|
||||
Content = viewer
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
window.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
viewer.ApplyTemplate();
|
||||
|
||||
var editor = viewer.GetVisualDescendants()
|
||||
.OfType<TextEditor>()
|
||||
.Single();
|
||||
editor.ApplyTemplate();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.ShouldNotBeEmpty();
|
||||
|
||||
var startPoint = editor.TextArea.TextView.TranslatePoint(new Point(80, 22), window)
|
||||
.ShouldNotBeNull();
|
||||
|
||||
window.MouseMove(startPoint);
|
||||
window.MouseDown(startPoint, MouseButton.Left);
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.ShouldNotBeEmpty();
|
||||
|
||||
window.MouseUp(startPoint, MouseButton.Left);
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.ShouldNotBeEmpty();
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
viewer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GalleryCodeViewer_Keeps_TextMate_During_Pointer_Selection_Drag()
|
||||
{
|
||||
var viewer = new GalleryCodeViewer
|
||||
{
|
||||
CodeText = string.Join(
|
||||
"\n",
|
||||
Enumerable.Repeat(
|
||||
" <atom:TextBlock Foreground=\"{atom:SharedTokenResource ColorTextTertiary}\" Text=\"{Binding Description}\" />",
|
||||
40)),
|
||||
Language = "axaml"
|
||||
};
|
||||
var window = new Window
|
||||
{
|
||||
Width = 640,
|
||||
Height = 480,
|
||||
Content = viewer
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
window.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
viewer.ApplyTemplate();
|
||||
|
||||
var editor = viewer.GetVisualDescendants()
|
||||
.OfType<TextEditor>()
|
||||
.Single();
|
||||
editor.ApplyTemplate();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.ShouldNotBeEmpty();
|
||||
|
||||
var textView = editor.TextArea.TextView;
|
||||
var startPoint = textView.TranslatePoint(new Point(80, 22), window)
|
||||
.ShouldNotBeNull();
|
||||
var dragPoint = textView.TranslatePoint(new Point(180, 52), window)
|
||||
.ShouldNotBeNull();
|
||||
|
||||
window.MouseMove(startPoint);
|
||||
window.MouseDown(startPoint, MouseButton.Left);
|
||||
window.MouseMove(dragPoint);
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.ShouldNotBeEmpty();
|
||||
|
||||
window.MouseUp(dragPoint, MouseButton.Left);
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
editor.TextArea.TextView.LineTransformers
|
||||
.OfType<TextMateColoringTransformer>()
|
||||
.ShouldNotBeEmpty();
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
viewer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GalleryCodeViewer_Uses_Dark_Syntax_Theme_When_AtomUI_Dark_Mode_Is_Enabled()
|
||||
{
|
||||
@@ -607,6 +796,16 @@ public class GalleryCodeViewerRuntimeTests
|
||||
return syntaxThemeField.GetValue(viewer).ShouldBeOfType<ThemeName>();
|
||||
}
|
||||
|
||||
private static Avalonia.Controls.ScrollViewer GetEditorScrollViewer(TextEditor editor)
|
||||
{
|
||||
var scrollViewerProperty = typeof(TextEditor).GetProperty(
|
||||
"ScrollViewer",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
scrollViewerProperty.ShouldNotBeNull();
|
||||
|
||||
return scrollViewerProperty.GetValue(editor).ShouldBeOfType<Avalonia.Controls.ScrollViewer>();
|
||||
}
|
||||
|
||||
private static string? GetAxamlTagNameColor(GalleryCodeViewer viewer)
|
||||
{
|
||||
return GetAxamlTokenColor(viewer, "StackPanel");
|
||||
|
||||
+28
@@ -54,6 +54,34 @@ public class GallerySourceCodeDisplayLayoutTests
|
||||
viewerMarkup.ShouldContain("x:Key=\"ThemeForegroundColor\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Code_Viewer_Does_Not_Keep_A_Permanent_LayoutUpdated_Scrollbar_Adjuster()
|
||||
{
|
||||
var viewerSource = File.ReadAllText(GetRepoFile("src/AtomUI.Toolkits.GalleryBase/Controls/GalleryCodeViewer.cs"));
|
||||
|
||||
var requestMethodIndex = viewerSource.IndexOf(
|
||||
"private void RequestHorizontalScrollBarGutterInsetUpdate()",
|
||||
StringComparison.Ordinal);
|
||||
var subscriptionIndex = viewerSource.IndexOf(
|
||||
"_editor.LayoutUpdated += HandleEditorLayoutUpdated",
|
||||
StringComparison.Ordinal);
|
||||
var handlerIndex = viewerSource.IndexOf(
|
||||
"private void HandleEditorLayoutUpdated",
|
||||
StringComparison.Ordinal);
|
||||
var cancelInHandlerIndex = viewerSource.IndexOf(
|
||||
"CancelHorizontalScrollBarGutterInsetUpdate();",
|
||||
handlerIndex,
|
||||
StringComparison.Ordinal);
|
||||
var updateInHandlerIndex = viewerSource.IndexOf(
|
||||
"UpdateHorizontalScrollBarGutterInset();",
|
||||
handlerIndex,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
requestMethodIndex.ShouldBeGreaterThanOrEqualTo(0);
|
||||
subscriptionIndex.ShouldBeGreaterThan(requestMethodIndex);
|
||||
cancelInHandlerIndex.ShouldBeLessThan(updateInHandlerIndex);
|
||||
}
|
||||
|
||||
private static string GetRepoFile(string relativePath)
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
|
||||
@@ -50,7 +50,7 @@ public class BorderBeamShowCasePageTests
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource PageSubtitle");
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource PageDescription");
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource ComponentCategory");
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource ComponentStatusPreview");
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource ComponentIntroducedVersion");
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource ScenarioExamples");
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource ScenarioApi");
|
||||
source.ShouldContain("BorderBeamShowCaseLangResource ScenarioDesignToken");
|
||||
@@ -68,6 +68,30 @@ public class BorderBeamShowCasePageTests
|
||||
source.ShouldNotContain("<atom:DataGrid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BorderBeam_ShowCase_Header_Centers_Title_Tags_And_Uses_Blue_Introduced_Version_Tag()
|
||||
{
|
||||
var source = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/BorderBeam/Views/BorderBeamShowCase.axaml");
|
||||
var enSource = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/BorderBeam/Localization/en_US.cs");
|
||||
var zhCnSource = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/BorderBeam/Localization/zh_CN.cs");
|
||||
var zhTwSource = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/BorderBeam/Localization/zh_TW.cs");
|
||||
var header = ExtractHeaderTitleMarkup(source);
|
||||
|
||||
header.ShouldContain("<Grid ColumnDefinitions=\"Auto,*\"");
|
||||
header.ShouldContain("<WrapPanel Grid.Column=\"1\"");
|
||||
header.ShouldContain("VerticalAlignment=\"Center\"");
|
||||
header.ShouldContain("Text=\"{gallery:BorderBeamShowCaseLangResource ComponentIntroducedVersion}\"");
|
||||
header.ShouldContain("TagColor=\"blue\"");
|
||||
header.ShouldContain("IsBordered=\"False\"");
|
||||
header.ShouldNotContain("ComponentStatusPreview");
|
||||
|
||||
foreach (var localizationSource in new[] { enSource, zhCnSource, zhTwSource })
|
||||
{
|
||||
localizationSource.ShouldContain("public const string ComponentIntroducedVersion = \"v6.0.5\";");
|
||||
localizationSource.ShouldNotContain("ComponentStatusPreview");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BorderBeam_ShowCase_Demos_Match_Ant_Design_Examples()
|
||||
{
|
||||
@@ -189,6 +213,21 @@ public class BorderBeamShowCasePageTests
|
||||
return source[itemStart..(itemEnd + itemEndMarker.Length)];
|
||||
}
|
||||
|
||||
private static string ExtractHeaderTitleMarkup(string source)
|
||||
{
|
||||
const string headerStartMarker = "Text=\"BorderBeam\"";
|
||||
const string headerEndMarker = "BorderBeamShowCaseLangResource PageSubtitle";
|
||||
|
||||
var titleIndex = source.IndexOf(headerStartMarker, StringComparison.Ordinal);
|
||||
titleIndex.ShouldBeGreaterThanOrEqualTo(0);
|
||||
var headerStart = source.LastIndexOf("<Grid", titleIndex, StringComparison.Ordinal);
|
||||
headerStart.ShouldBeGreaterThanOrEqualTo(0);
|
||||
var headerEnd = source.IndexOf(headerEndMarker, headerStart, StringComparison.Ordinal);
|
||||
headerEnd.ShouldBeGreaterThan(headerStart);
|
||||
|
||||
return source[headerStart..headerEnd];
|
||||
}
|
||||
|
||||
private static IEnumerable<GalleryNavigationNode> Walk(IEnumerable<GalleryNavigationNode> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
|
||||
@@ -50,6 +50,7 @@ public class SplashShowCasePageTests
|
||||
source.ShouldContain("SplashShowCaseLangResource PageDescription");
|
||||
source.ShouldContain("SplashShowCaseLangResource ComponentCategory");
|
||||
source.ShouldContain("SplashShowCaseLangResource ComponentStatusPreview");
|
||||
source.ShouldContain("SplashShowCaseLangResource ComponentIntroducedVersion");
|
||||
source.ShouldContain("SplashShowCaseLangResource ScenarioExamples");
|
||||
source.ShouldContain("SplashShowCaseLangResource ScenarioApi");
|
||||
source.ShouldContain("SplashShowCaseLangResource ScenarioDesignToken");
|
||||
@@ -67,6 +68,29 @@ public class SplashShowCasePageTests
|
||||
source.ShouldNotContain("<atom:DataGrid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Splash_ShowCase_Header_Centers_Title_Tags_And_Adds_Blue_Introduced_Version_Tag()
|
||||
{
|
||||
var source = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/Splash/Views/SplashShowCase.axaml");
|
||||
var enSource = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/Splash/Localization/en_US.cs");
|
||||
var zhCnSource = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/Splash/Localization/zh_CN.cs");
|
||||
var zhTwSource = ReadRepoFile("controlgallery/AtomUIGallery/ShowCases/Other/Splash/Localization/zh_TW.cs");
|
||||
var header = ExtractHeaderTitleMarkup(source);
|
||||
|
||||
header.ShouldContain("<Grid ColumnDefinitions=\"Auto,*\"");
|
||||
header.ShouldContain("<WrapPanel Grid.Column=\"1\"");
|
||||
header.ShouldContain("VerticalAlignment=\"Center\"");
|
||||
header.ShouldContain("Text=\"{gallery:SplashShowCaseLangResource ComponentStatusPreview}\"");
|
||||
header.ShouldContain("Text=\"{gallery:SplashShowCaseLangResource ComponentIntroducedVersion}\"");
|
||||
header.ShouldContain("TagColor=\"blue\"");
|
||||
header.ShouldContain("IsBordered=\"False\"");
|
||||
|
||||
foreach (var localizationSource in new[] { enSource, zhCnSource, zhTwSource })
|
||||
{
|
||||
localizationSource.ShouldContain("public const string ComponentIntroducedVersion = \"v6.0.7\";");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Splash_ShowCase_Demos_Cover_Visual_States_Without_Showing_Window()
|
||||
{
|
||||
@@ -257,6 +281,21 @@ public class SplashShowCasePageTests
|
||||
return source[itemStart..(itemEnd + itemEndMarker.Length)];
|
||||
}
|
||||
|
||||
private static string ExtractHeaderTitleMarkup(string source)
|
||||
{
|
||||
const string headerStartMarker = "Text=\"Splash\"";
|
||||
const string headerEndMarker = "SplashShowCaseLangResource PageSubtitle";
|
||||
|
||||
var titleIndex = source.IndexOf(headerStartMarker, StringComparison.Ordinal);
|
||||
titleIndex.ShouldBeGreaterThanOrEqualTo(0);
|
||||
var headerStart = source.LastIndexOf("<Grid", titleIndex, StringComparison.Ordinal);
|
||||
headerStart.ShouldBeGreaterThanOrEqualTo(0);
|
||||
var headerEnd = source.IndexOf(headerEndMarker, headerStart, StringComparison.Ordinal);
|
||||
headerEnd.ShouldBeGreaterThan(headerStart);
|
||||
|
||||
return source[headerStart..headerEnd];
|
||||
}
|
||||
|
||||
private static IEnumerable<GalleryNavigationNode> Walk(IEnumerable<GalleryNavigationNode> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
|
||||
Reference in New Issue
Block a user