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.props = build\Output.props
|
||||
build\Version.props = build\Version.props
|
||||
build\Directory.Build.targets = build\Directory.Build.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
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
|
||||
atom:Watermark.Glyph="{atom:TextGlyph 'Atom UI', Space=100, Angle=30, Opacity=0.5, UseMirror=False, UseCross=True}"
|
||||
Title="Button Shape"
|
||||
Description="Supported button shape display, such as primary, default, dashed and Text, etc.">
|
||||
<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