mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-29 18:38:16 +08:00
+ Watermark;
This commit is contained in:
parent
3521c3da11
commit
3036c31711
@ -31,6 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{5EB2524C
|
|||||||
build\Output.Local.props = build\Output.Local.props
|
build\Output.Local.props = build\Output.Local.props
|
||||||
build\Output.props = build\Output.props
|
build\Output.props = build\Output.props
|
||||||
build\Version.props = build\Version.props
|
build\Version.props = build\Version.props
|
||||||
|
build\Directory.Build.targets = build\Directory.Build.targets
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{279BC2C9-A818-4D6C-9274-678649932057}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{279BC2C9-A818-4D6C-9274-678649932057}"
|
||||||
|
8
build/Directory.Build.targets
Normal file
8
build/Directory.Build.targets
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<Project>
|
||||||
|
|
||||||
|
<!-- https://github.com/dotnet/sdk/issues/22515-->
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="*.csproj.DotSettings" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -25,6 +25,7 @@
|
|||||||
</showcase:ShowCaseItem>
|
</showcase:ShowCaseItem>
|
||||||
|
|
||||||
<showcase:ShowCaseItem
|
<showcase:ShowCaseItem
|
||||||
|
atom:Watermark.Glyph="{atom:TextGlyph 'Atom UI', Space=100, Angle=30, Opacity=0.5, UseMirror=False, UseCross=True}"
|
||||||
Title="Button Shape"
|
Title="Button Shape"
|
||||||
Description="Supported button shape display, such as primary, default, dashed and Text, etc.">
|
Description="Supported button shape display, such as primary, default, dashed and Text, etc.">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
|
3
src/AtomUI.Controls/AtomUI.Controls.csproj.DotSettings
Normal file
3
src/AtomUI.Controls/AtomUI.Controls.csproj.DotSettings
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=watermark/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=watermark_005Cglyphs/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
75
src/AtomUI.Controls/Primitives/AtomLayer.cs
Normal file
75
src/AtomUI.Controls/Primitives/AtomLayer.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
|
// ReSharper disable SuggestBaseTypeForParameter
|
||||||
|
|
||||||
|
namespace AtomUI.Controls.Primitives
|
||||||
|
{
|
||||||
|
public static class AtomLayerExtension
|
||||||
|
{
|
||||||
|
public static AtomLayer? GetAxLayer(this Visual? visual)
|
||||||
|
{
|
||||||
|
return visual == null ? null : AtomLayer.GetLayer(visual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AtomLayer : Panel
|
||||||
|
{
|
||||||
|
private AtomLayer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AtomLayer? GetLayer(Visual visual)
|
||||||
|
{
|
||||||
|
if (visual.GetVisualRoot() is not TopLevel topLevel)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer = topLevel.GetVisualChildren().FirstOrDefault(c => c is AtomLayer) as AtomLayer;
|
||||||
|
layer ??= TryInject(topLevel);
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AtomLayer TryInject(TopLevel topLevel)
|
||||||
|
{
|
||||||
|
var layer = new AtomLayer();
|
||||||
|
|
||||||
|
if (topLevel.IsLoaded == false)
|
||||||
|
{
|
||||||
|
topLevel.Loaded += (sender, args) =>
|
||||||
|
{
|
||||||
|
InjectCore(topLevel, layer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InjectCore(topLevel, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InjectCore(TopLevel topLevel, AtomLayer layer)
|
||||||
|
{
|
||||||
|
if (topLevel.GetVisualChildren() is not IList<Visual> visualChildren)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (visualChildren.Any(c => c is AtomLayer))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||||
|
layer.VerticalAlignment = VerticalAlignment.Stretch;
|
||||||
|
layer.InheritanceParent = topLevel;
|
||||||
|
|
||||||
|
visualChildren.Add(layer);
|
||||||
|
topLevel.InvalidateMeasure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
src/AtomUI.Controls/Utils/MatrixUtil.cs
Normal file
59
src/AtomUI.Controls/Utils/MatrixUtil.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using Avalonia;
|
||||||
|
|
||||||
|
namespace AtomUI.Controls.Utils
|
||||||
|
{
|
||||||
|
public static class MatrixUtil
|
||||||
|
{
|
||||||
|
public static void ScaleAt(ref this Matrix matrix, double scaleX, double scaleY, double centerX, double centerY)
|
||||||
|
{
|
||||||
|
matrix *= CreateScaling(scaleX, scaleY, centerX, centerY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Translate(ref this Matrix matrix, double offsetX, double offsetY)
|
||||||
|
{
|
||||||
|
matrix = new Matrix(matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.M31 + offsetX, matrix.M32 + offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RotateAt(ref this Matrix matrix, double angle, double centerX, double centerY)
|
||||||
|
{
|
||||||
|
matrix *= CreateRotationRadians(angle, centerX, centerY);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static Matrix CreateRotationRadians(double angle, double centerX, double centerY)
|
||||||
|
{
|
||||||
|
var m12 = Math.Sin(angle);
|
||||||
|
var num = Math.Cos(angle);
|
||||||
|
var offsetX = centerX * (1.0 - num) + centerY * m12;
|
||||||
|
var offsetY = centerY * (1.0 - num) - centerX * m12;
|
||||||
|
|
||||||
|
return new Matrix(num, m12, -m12, num, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix CreateScaling(double scaleX, double scaleY, double centerX, double centerY)
|
||||||
|
{
|
||||||
|
return new Matrix(scaleX, 0.0, 0.0, scaleY, centerX - scaleX * centerX, centerY - scaleY * centerY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix CreateScaling(double scaleX, double scaleY)
|
||||||
|
{
|
||||||
|
return new Matrix(scaleX, 0.0, 0.0, scaleY, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix CreateSkewRadians(double skewX, double skewY)
|
||||||
|
{
|
||||||
|
return new Matrix(1.0, Math.Tan(skewY), Math.Tan(skewX), 1.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix CreateTranslation(double offsetX, double offsetY)
|
||||||
|
{
|
||||||
|
return new Matrix(1.0, 0.0, 0.0, 1.0, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix CreateIdentity()
|
||||||
|
{
|
||||||
|
return new Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/AtomUI.Controls/Watermark/Glyphs/TextGlyph.cs
Normal file
53
src/AtomUI.Controls/Watermark/Glyphs/TextGlyph.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace AtomUI.Controls;
|
||||||
|
|
||||||
|
public class TextGlyph : WatermarkGlyph
|
||||||
|
{
|
||||||
|
public string? Text
|
||||||
|
{
|
||||||
|
get => GetValue(TextProperty);
|
||||||
|
set => SetValue(TextProperty, value);
|
||||||
|
}
|
||||||
|
public static readonly StyledProperty<string?> TextProperty = AvaloniaProperty
|
||||||
|
.Register<TextGlyph, string?>(nameof(Text));
|
||||||
|
|
||||||
|
protected FormattedText? FormattedText { get; private set; }
|
||||||
|
|
||||||
|
static TextGlyph()
|
||||||
|
{
|
||||||
|
TextProperty.Changed.AddClassHandler<TextGlyph>((glyph, args) =>
|
||||||
|
{
|
||||||
|
glyph.RebuildFormatText();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildFormatText()
|
||||||
|
{
|
||||||
|
if (Text == null)
|
||||||
|
{
|
||||||
|
FormattedText = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Expose properties.
|
||||||
|
FormattedText = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, 15, Brushes.Gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
if (FormattedText == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.DrawText(FormattedText, new Point());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Size GetDesiredSize()
|
||||||
|
{
|
||||||
|
return FormattedText == null ? new Size() : new Size(FormattedText.Width, FormattedText.Height);
|
||||||
|
}
|
||||||
|
}
|
23
src/AtomUI.Controls/Watermark/Glyphs/TextGlyphExtension.cs
Normal file
23
src/AtomUI.Controls/Watermark/Glyphs/TextGlyphExtension.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace AtomUI.Controls;
|
||||||
|
|
||||||
|
public class TextGlyphExtension : WatermarkGlyphExtension
|
||||||
|
{
|
||||||
|
private string? Text { get; }
|
||||||
|
|
||||||
|
public TextGlyphExtension(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var glyph = new TextGlyph()
|
||||||
|
{
|
||||||
|
Text = Text,
|
||||||
|
};
|
||||||
|
|
||||||
|
SetProperties(glyph);
|
||||||
|
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace AtomUI.Controls;
|
||||||
|
|
||||||
|
public abstract class WatermarkGlyphExtension : MarkupExtension
|
||||||
|
{
|
||||||
|
public double Space { get; set; }
|
||||||
|
|
||||||
|
public double Angle { get; set; }
|
||||||
|
|
||||||
|
public double Opacity { get; set; }
|
||||||
|
|
||||||
|
public bool UseMirror { get; set; }
|
||||||
|
|
||||||
|
public bool UseCross { get; set; }
|
||||||
|
|
||||||
|
protected void SetProperties(WatermarkGlyph glyph)
|
||||||
|
{
|
||||||
|
glyph.Space = Space;
|
||||||
|
glyph.Angle = Angle;
|
||||||
|
glyph.Opacity = Opacity;
|
||||||
|
glyph.UseMirror = UseMirror;
|
||||||
|
glyph.UseCross = UseCross;
|
||||||
|
}
|
||||||
|
}
|
167
src/AtomUI.Controls/Watermark/Watermark.cs
Normal file
167
src/AtomUI.Controls/Watermark/Watermark.cs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using AtomUI.Controls.Primitives;
|
||||||
|
using AtomUI.Controls.Utils;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
|
namespace AtomUI.Controls;
|
||||||
|
|
||||||
|
public sealed class Watermark : Control
|
||||||
|
{
|
||||||
|
public static WatermarkGlyph? GetGlyph(Visual element)
|
||||||
|
{
|
||||||
|
return element.GetValue(GlyphProperty);
|
||||||
|
}
|
||||||
|
public static void SetGlyph(Visual element, WatermarkGlyph? value)
|
||||||
|
{
|
||||||
|
element.SetValue(GlyphProperty, value);
|
||||||
|
}
|
||||||
|
public static readonly AttachedProperty<WatermarkGlyph?> GlyphProperty = AvaloniaProperty
|
||||||
|
.RegisterAttached<Watermark, Visual, WatermarkGlyph?>("Glyph");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private Visual? Target { get; init; }
|
||||||
|
|
||||||
|
private WatermarkGlyph? Glyph { get; init; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static Watermark()
|
||||||
|
{
|
||||||
|
GlyphProperty.Changed.AddClassHandler<Visual>(OnGlyphChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Watermark()
|
||||||
|
{
|
||||||
|
IsHitTestVisibleProperty.OverrideMetadata<Watermark>(new StyledPropertyMetadata<bool>(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnGlyphChanged(Visual target, AvaloniaPropertyChangedEventArgs arg)
|
||||||
|
{
|
||||||
|
if (target.IsAttachedToVisualTree())
|
||||||
|
{
|
||||||
|
InstallWatermark(target);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.AttachedToVisualTree += TargetOnAttachedToVisualTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TargetOnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Visual target)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.AttachedToVisualTree -= TargetOnAttachedToVisualTree;
|
||||||
|
|
||||||
|
InstallWatermark(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InstallWatermark(Visual target)
|
||||||
|
{
|
||||||
|
if (CheckLayer(target, out var layer) == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var watermark = layer.Children.OfType<Watermark>().FirstOrDefault(w => w.Target == target);
|
||||||
|
if (watermark != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
watermark = new Watermark
|
||||||
|
{
|
||||||
|
Target = target,
|
||||||
|
Glyph = GetGlyph(target),
|
||||||
|
};
|
||||||
|
layer.Children.Add(watermark);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CheckLayer(Visual target, [NotNullWhen(true)] out AtomLayer? layer)
|
||||||
|
{
|
||||||
|
layer = target.GetAxLayer();
|
||||||
|
if (layer == null)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"Can not get AxLayer for {target} to show a watermark.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
base.Render(context);
|
||||||
|
|
||||||
|
if (Target == null || Glyph == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CheckLayer(Target, out var layer) == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matrix = Target.TransformToVisual(layer);
|
||||||
|
if (matrix == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = Glyph.GetDesiredSize();
|
||||||
|
if (size.Width == 0 || size.Height == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (context.PushTransform(matrix.Value))
|
||||||
|
using (context.PushClip(new Rect(Target.Bounds.Size)))
|
||||||
|
using (context.PushOpacity(Glyph.Opacity))
|
||||||
|
{
|
||||||
|
var t = 0d;
|
||||||
|
var r = 0;
|
||||||
|
while (t < Target.Bounds.Height)
|
||||||
|
{
|
||||||
|
var pushState = new DrawingContext.PushedState();
|
||||||
|
if (r % 2 == 1 && Glyph.UseCross)
|
||||||
|
{
|
||||||
|
pushState = context.PushTransform(Matrix.CreateTranslation((Glyph.Space - size.Width) / 2 + size.Width, 0));
|
||||||
|
}
|
||||||
|
using (pushState)
|
||||||
|
{
|
||||||
|
var l = 0d;
|
||||||
|
var c = 0;
|
||||||
|
while (l < Target.Bounds.Width)
|
||||||
|
{
|
||||||
|
var angle = Glyph.Angle;
|
||||||
|
if (c % 2 == 1 && Glyph.UseMirror)
|
||||||
|
{
|
||||||
|
angle = -angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = MatrixUtil.CreateRotationRadians(angle * Math.PI / 180, size.Width / 2, size.Height / 2);
|
||||||
|
using (context.PushTransform(Matrix.CreateTranslation(l, t)))
|
||||||
|
using (context.PushTransform(m))
|
||||||
|
{
|
||||||
|
Glyph.Render(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
l += size.Width + Glyph.Space;
|
||||||
|
c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
t += size.Height + Glyph.Space;
|
||||||
|
r++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/AtomUI.Controls/Watermark/WatermarkGlyph.cs
Normal file
51
src/AtomUI.Controls/Watermark/WatermarkGlyph.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace AtomUI.Controls;
|
||||||
|
|
||||||
|
public abstract class WatermarkGlyph : AvaloniaObject
|
||||||
|
{
|
||||||
|
public double Space
|
||||||
|
{
|
||||||
|
get => GetValue(SpaceProperty);
|
||||||
|
set => SetValue(SpaceProperty, value);
|
||||||
|
}
|
||||||
|
public static readonly StyledProperty<double> SpaceProperty = AvaloniaProperty
|
||||||
|
.Register<WatermarkGlyph, double>(nameof(Space));
|
||||||
|
|
||||||
|
public double Angle
|
||||||
|
{
|
||||||
|
get => GetValue(AngleProperty);
|
||||||
|
set => SetValue(AngleProperty, value);
|
||||||
|
}
|
||||||
|
public static readonly StyledProperty<double> AngleProperty = AvaloniaProperty
|
||||||
|
.Register<WatermarkGlyph, double>(nameof(Angle));
|
||||||
|
|
||||||
|
public double Opacity
|
||||||
|
{
|
||||||
|
get => GetValue(OpacityProperty);
|
||||||
|
set => SetValue(OpacityProperty, value);
|
||||||
|
}
|
||||||
|
public static readonly StyledProperty<double> OpacityProperty = AvaloniaProperty
|
||||||
|
.Register<WatermarkGlyph, double>(nameof(Opacity));
|
||||||
|
|
||||||
|
public bool UseMirror
|
||||||
|
{
|
||||||
|
get => GetValue(UseMirrorProperty);
|
||||||
|
set => SetValue(UseMirrorProperty, value);
|
||||||
|
}
|
||||||
|
public static readonly StyledProperty<bool> UseMirrorProperty = AvaloniaProperty
|
||||||
|
.Register<WatermarkGlyph, bool>(nameof(UseMirror));
|
||||||
|
|
||||||
|
public bool UseCross
|
||||||
|
{
|
||||||
|
get => GetValue(UseCrossProperty);
|
||||||
|
set => SetValue(UseCrossProperty, value);
|
||||||
|
}
|
||||||
|
public static readonly StyledProperty<bool> UseCrossProperty = AvaloniaProperty
|
||||||
|
.Register<WatermarkGlyph, bool>(nameof(UseCross));
|
||||||
|
|
||||||
|
public abstract void Render(DrawingContext context);
|
||||||
|
|
||||||
|
public abstract Size GetDesiredSize();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user