committed by
GitHub
209 changed files with 2131 additions and 4557 deletions
@ -1,7 +1,3 @@ |
|||||
[submodule "src/Avalonia.HtmlRenderer/external"] |
|
||||
path = src/Avalonia.HtmlRenderer/external |
|
||||
url = https://github.com/AvaloniaUI/HTML-Renderer.git |
|
||||
branch = perspex-pcl |
|
||||
[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"] |
[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"] |
||||
path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github |
path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github |
||||
url = https://github.com/AvaloniaUI/Portable.Xaml.git |
url = https://github.com/AvaloniaUI/Portable.Xaml.git |
||||
|
|||||
@ -0,0 +1,8 @@ |
|||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" /> |
||||
|
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" /> |
||||
|
<PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" /> |
||||
|
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -1,17 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Text; |
|
||||
using System.Linq; |
|
||||
using System.Reactive.Linq; |
|
||||
using System.Diagnostics; |
|
||||
using Avalonia.Animation.Utils; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.Animation |
|
||||
{ |
|
||||
public abstract class AnimationSetter : IAnimationSetter |
|
||||
{ |
|
||||
public AvaloniaProperty Property { get; set; } |
|
||||
public object Value { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace Avalonia.Animation |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Attribute for <see cref="IAnimationSetter"/> objects
|
|
||||
/// that maps the setter to it's <see cref="Animator{T}"/>.
|
|
||||
/// </summary>
|
|
||||
public class AnimatorAttribute : Attribute |
|
||||
{ |
|
||||
public Type HandlerType; |
|
||||
|
|
||||
public AnimatorAttribute(Type handler) |
|
||||
{ |
|
||||
this.HandlerType = handler; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Text; |
|
||||
using System.Linq; |
|
||||
using System.Reactive.Linq; |
|
||||
using System.Diagnostics; |
|
||||
using Avalonia.Animation.Utils; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.Animation |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Setter that handles <see cref="double"/> properties
|
|
||||
/// in the target.
|
|
||||
/// </summary>
|
|
||||
[Animator(typeof(DoubleAnimator))] |
|
||||
public class DoubleSetter : AnimationSetter |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
namespace Avalonia.Markup.Data |
namespace Avalonia.Data.Core |
||||
{ |
{ |
||||
public static class CommonPropertyNames |
public static class CommonPropertyNames |
||||
{ |
{ |
||||
@ -1,9 +1,8 @@ |
|||||
using System; |
using System; |
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
using System.Linq; |
using System.Linq; |
||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.Markup.Data |
namespace Avalonia.Data.Core |
||||
{ |
{ |
||||
internal class MarkupBindingChainException : BindingChainException |
internal class MarkupBindingChainException : BindingChainException |
||||
{ |
{ |
||||
@ -1,9 +1,10 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
using System.Reflection; |
using System.Reflection; |
||||
using System.Runtime.CompilerServices; |
using System.Runtime.CompilerServices; |
||||
|
using Avalonia.Metadata; |
||||
|
|
||||
|
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")] |
||||
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] |
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] |
||||
[assembly: InternalsVisibleTo("Avalonia.UnitTests")] |
[assembly: InternalsVisibleTo("Avalonia.UnitTests")] |
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] |
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] |
||||
@ -1,12 +1,45 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
namespace Avalonia.Controls |
namespace Avalonia.Controls |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// A selectable item in a <see cref="DropDown"/>.
|
/// A selectable item in a <see cref="DropDown"/>.
|
||||
/// </summary>
|
/// </summary>
|
||||
public class DropDownItem : ListBoxItem |
public class DropDownItem : ContentControl, ISelectable |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="IsSelected"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<bool> IsSelectedProperty = |
||||
|
AvaloniaProperty.Register<DropDownItem, bool>(nameof(IsSelected)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes static members of the <see cref="DropDownItem"/> class.
|
||||
|
/// </summary>
|
||||
|
static DropDownItem() |
||||
|
{ |
||||
|
FocusableProperty.OverrideDefaultValue<DropDownItem>(true); |
||||
|
IsFocusedProperty.Changed.Subscribe(x => |
||||
|
{ |
||||
|
var sender = x.Sender as IControl; |
||||
|
|
||||
|
if (sender != null) |
||||
|
{ |
||||
|
((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the selection state of the item.
|
||||
|
/// </summary>
|
||||
|
public bool IsSelected |
||||
|
{ |
||||
|
get { return GetValue(IsSelectedProperty); } |
||||
|
set { SetValue(IsSelectedProperty, value); } |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,129 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.IO; |
|
||||
using System.Linq; |
|
||||
using System.Reflection; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
using Avalonia; |
|
||||
using Avalonia.Input.Platform; |
|
||||
using Avalonia.Media; |
|
||||
using Avalonia.Media.Imaging; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
class AvaloniaAdapter : RAdapter |
|
||||
{ |
|
||||
public static AvaloniaAdapter Instance { get; } = new AvaloniaAdapter(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// List of valid predefined color names in lower-case
|
|
||||
/// </summary>
|
|
||||
private static readonly Dictionary<string, Color> ColorNameDic = new Dictionary<string, Color>(); |
|
||||
|
|
||||
|
|
||||
static AvaloniaAdapter() |
|
||||
{ |
|
||||
foreach (var colorProp in typeof(Colors).GetRuntimeProperties() |
|
||||
.Where(p=>p.PropertyType == typeof(Color))) |
|
||||
{ |
|
||||
ColorNameDic[colorProp.Name.ToLower()] = (Color)colorProp.GetValue(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected override RColor GetColorInt(string colorName) |
|
||||
{ |
|
||||
Color c; |
|
||||
if(!ColorNameDic.TryGetValue(colorName.ToLower(), out c)) |
|
||||
return RColor.Empty; |
|
||||
return Util.Convert(c); |
|
||||
} |
|
||||
|
|
||||
protected override RPen CreatePen(RColor color) |
|
||||
{ |
|
||||
return new PenAdapter(GetSolidColorBrush(color)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get solid color brush for the given color.
|
|
||||
/// </summary>
|
|
||||
private static IBrush GetSolidColorBrush(RColor color) |
|
||||
{ |
|
||||
IBrush solidBrush; |
|
||||
if (color == RColor.White) |
|
||||
solidBrush = Brushes.White; |
|
||||
else if (color == RColor.Black) |
|
||||
solidBrush = Brushes.Black; |
|
||||
else if (color.A < 1) |
|
||||
solidBrush = Brushes.Transparent; |
|
||||
else |
|
||||
solidBrush = new SolidColorBrush(Util.Convert(color)); |
|
||||
return solidBrush; |
|
||||
} |
|
||||
|
|
||||
protected override RBrush CreateSolidBrush(RColor color) |
|
||||
{ |
|
||||
return new BrushAdapter(GetSolidColorBrush(color)); |
|
||||
} |
|
||||
|
|
||||
protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle) |
|
||||
{ |
|
||||
var startColor = angle <= 180 ? Util.Convert(color1) : Util.Convert(color2); |
|
||||
var endColor = angle <= 180 ? Util.Convert(color2) : Util.Convert(color1); |
|
||||
angle = angle <= 180 ? angle : angle - 180; |
|
||||
double x = angle < 135 ? Math.Max((angle - 45) / 90, 0) : 1; |
|
||||
double y = angle <= 45 ? Math.Max(0.5 - angle / 90, 0) : angle > 135 ? Math.Abs(1.5 - angle / 90) : 0; |
|
||||
return new BrushAdapter(new LinearGradientBrush |
|
||||
{ |
|
||||
StartPoint = new RelativePoint(x, y, RelativeUnit.Relative), |
|
||||
EndPoint = new RelativePoint(1 - x, 1 - y, RelativeUnit.Relative), |
|
||||
GradientStops = new[] |
|
||||
{ |
|
||||
new GradientStop(startColor, 0), |
|
||||
new GradientStop(endColor, 1) |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
protected override RImage ConvertImageInt(object image) |
|
||||
{ |
|
||||
return image != null ? new ImageAdapter((Bitmap)image) : null; |
|
||||
} |
|
||||
|
|
||||
protected override RImage ImageFromStreamInt(Stream memoryStream) |
|
||||
{ |
|
||||
//TODO: Implement bitmap loader
|
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
protected override RFont CreateFontInt(string family, double size, RFontStyle style) |
|
||||
{ |
|
||||
return new FontAdapter(family, size, style); |
|
||||
} |
|
||||
|
|
||||
protected override RFont CreateFontInt(RFontFamily family, double size, RFontStyle style) |
|
||||
{ |
|
||||
return new FontAdapter(family.Name, size, style); |
|
||||
} |
|
||||
|
|
||||
protected override void SetToClipboardInt(string html, string plainText) |
|
||||
{ |
|
||||
SetToClipboardInt(plainText); |
|
||||
} |
|
||||
|
|
||||
protected override void SetToClipboardInt(string text) |
|
||||
{ |
|
||||
AvaloniaLocator.Current.GetService<IClipboard>().SetTextAsync(text); |
|
||||
} |
|
||||
|
|
||||
protected override void SetToClipboardInt(RImage image) |
|
||||
{ |
|
||||
//Do not crash, just ignore
|
|
||||
//TODO: implement image clipboard support
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,47 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia brushes.
|
|
||||
/// </summary>
|
|
||||
internal sealed class BrushAdapter : RBrush |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The actual Avalonia brush instance.
|
|
||||
/// </summary>
|
|
||||
private readonly IBrush _brush; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
public BrushAdapter(IBrush brush) |
|
||||
{ |
|
||||
_brush = brush; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The actual Avalonia brush instance.
|
|
||||
/// </summary>
|
|
||||
public IBrush Brush |
|
||||
{ |
|
||||
get { return _brush; } |
|
||||
} |
|
||||
|
|
||||
public override void Dispose() |
|
||||
{ } |
|
||||
} |
|
||||
} |
|
||||
@ -1,51 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using System; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia context menu for core.
|
|
||||
/// </summary>
|
|
||||
internal sealed class NullContextMenuAdapter : RContextMenu |
|
||||
{ |
|
||||
//TODO: actually implement context menu
|
|
||||
|
|
||||
private int _itemCount; |
|
||||
public override int ItemsCount => _itemCount; |
|
||||
public override void AddDivider() |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public override void AddItem(string text, bool enabled, EventHandler onClick) |
|
||||
{ |
|
||||
_itemCount++; |
|
||||
} |
|
||||
|
|
||||
public override void RemoveLastDivider() |
|
||||
{ |
|
||||
_itemCount++; |
|
||||
} |
|
||||
|
|
||||
public override void Show(RControl parent, RPoint location) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override void Dispose() |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,111 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using Avalonia; |
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Controls.Html; |
|
||||
using Avalonia.Input; |
|
||||
using Avalonia.VisualTree; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
using TheArtOfDev.HtmlRenderer.Core.Utils; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities; |
|
||||
// ReSharper disable ConvertPropertyToExpressionBody
|
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia Control for core.
|
|
||||
/// </summary>
|
|
||||
internal sealed class ControlAdapter : RControl |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// the underline Avalonia control.
|
|
||||
/// </summary>
|
|
||||
private readonly Control _control; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
public ControlAdapter(Control control) |
|
||||
: base(AvaloniaAdapter.Instance) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(control, "control"); |
|
||||
|
|
||||
_control = control; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the underline Avalonia control
|
|
||||
/// </summary>
|
|
||||
public Control Control |
|
||||
{ |
|
||||
get { return _control; } |
|
||||
} |
|
||||
|
|
||||
public override RPoint MouseLocation |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
var pos = (_control.GetVisualRoot() as IInputRoot)?.MouseDevice?.Position ?? default(Point); |
|
||||
return Util.Convert(pos); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private bool _leftMouseButton; |
|
||||
public override bool LeftMouseButton => (_control as HtmlControl)?.LeftMouseButton ?? false; |
|
||||
|
|
||||
public override bool RightMouseButton |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
return false; |
|
||||
//TODO: Implement right mouse click
|
|
||||
//return Mouse.RightButton == MouseButtonState.Pressed;
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public override void SetCursorDefault() |
|
||||
{ |
|
||||
_control.Cursor = new Cursor(StandardCursorType.Arrow); |
|
||||
} |
|
||||
|
|
||||
public override void SetCursorHand() |
|
||||
{ |
|
||||
_control.Cursor = new Cursor(StandardCursorType.Hand); |
|
||||
} |
|
||||
|
|
||||
public override void SetCursorIBeam() |
|
||||
{ |
|
||||
_control.Cursor = new Cursor(StandardCursorType.Ibeam); |
|
||||
} |
|
||||
|
|
||||
public override void DoDragDropCopy(object dragDropData) |
|
||||
{ |
|
||||
//TODO: Implement DragDropCopy
|
|
||||
//DragDrop.DoDragDrop(_control, dragDropData, DragDropEffects.Copy);
|
|
||||
} |
|
||||
|
|
||||
public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth) |
|
||||
{ |
|
||||
using (var g = new GraphicsAdapter()) |
|
||||
{ |
|
||||
g.MeasureString(str, font, maxWidth, out charFit, out charFitWidth); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public override void Invalidate() |
|
||||
{ |
|
||||
_control.InvalidateVisual(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,106 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia Font.
|
|
||||
/// </summary>
|
|
||||
internal sealed class FontAdapter : RFont |
|
||||
{ |
|
||||
public RFontStyle Style { get; } |
|
||||
|
|
||||
#region Fields and Consts
|
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// the size of the font
|
|
||||
/// </summary>
|
|
||||
private readonly double _size; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// the vertical offset of the font underline location from the top of the font.
|
|
||||
/// </summary>
|
|
||||
private readonly double _underlineOffset = -1; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Cached font height.
|
|
||||
/// </summary>
|
|
||||
private readonly double _height = -1; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Cached font whitespace width.
|
|
||||
/// </summary>
|
|
||||
private double _whitespaceWidth = -1; |
|
||||
|
|
||||
|
|
||||
#endregion
|
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
public FontAdapter(string fontFamily, double size, RFontStyle style) |
|
||||
{ |
|
||||
Style = style; |
|
||||
Name = fontFamily; |
|
||||
_size = size; |
|
||||
//TODO: Somehow get proper line spacing and underlinePosition
|
|
||||
var lineSpacing = 1; |
|
||||
var underlinePosition = 0; |
|
||||
|
|
||||
_height = 96d / 72d * _size * lineSpacing; |
|
||||
_underlineOffset = 96d / 72d * _size * (lineSpacing + underlinePosition); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public string Name { get; set; } |
|
||||
|
|
||||
|
|
||||
public override double Size |
|
||||
{ |
|
||||
get { return _size; } |
|
||||
} |
|
||||
|
|
||||
public override double UnderlineOffset |
|
||||
{ |
|
||||
get { return _underlineOffset; } |
|
||||
} |
|
||||
|
|
||||
public override double Height |
|
||||
{ |
|
||||
get { return _height; } |
|
||||
} |
|
||||
|
|
||||
public override double LeftPadding |
|
||||
{ |
|
||||
get { return _height / 6f; } |
|
||||
} |
|
||||
|
|
||||
public override double GetWhitespaceWidth(RGraphics graphics) |
|
||||
{ |
|
||||
if (_whitespaceWidth < 0) |
|
||||
{ |
|
||||
_whitespaceWidth = graphics.MeasureString(" ", this).Width; |
|
||||
} |
|
||||
return _whitespaceWidth; |
|
||||
} |
|
||||
|
|
||||
public FontStyle FontStyle => Style.HasFlag(RFontStyle.Italic) ? FontStyle.Italic : FontStyle.Normal; |
|
||||
|
|
||||
public FontWeight Weight => Style.HasFlag(RFontStyle.Bold) ? FontWeight.Bold : FontWeight.Normal; |
|
||||
} |
|
||||
} |
|
||||
@ -1,29 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia Font family object for core.
|
|
||||
/// </summary>
|
|
||||
internal sealed class FontFamilyAdapter : RFontFamily |
|
||||
{ |
|
||||
public FontFamilyAdapter(string fontFamily) |
|
||||
{ |
|
||||
Name = fontFamily; |
|
||||
} |
|
||||
|
|
||||
public override string Name { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,299 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Globalization; |
|
||||
using Avalonia; |
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
using TheArtOfDev.HtmlRenderer.Core.Utils; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia Graphics.
|
|
||||
/// </summary>
|
|
||||
internal sealed class GraphicsAdapter : RGraphics |
|
||||
{ |
|
||||
#region Fields and Consts
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The wrapped Avalonia graphics object
|
|
||||
/// </summary>
|
|
||||
private readonly DrawingContext _g; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// if to release the graphics object on dispose
|
|
||||
/// </summary>
|
|
||||
private readonly bool _releaseGraphics; |
|
||||
|
|
||||
#endregion
|
|
||||
|
|
||||
|
|
||||
private readonly Stack<IDisposable> _clipStack = new Stack<IDisposable>(); |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
/// <param name="g">the Avalonia graphics object to use</param>
|
|
||||
/// <param name="initialClip">the initial clip of the graphics</param>
|
|
||||
/// <param name="releaseGraphics">optional: if to release the graphics object on dispose (default - false)</param>
|
|
||||
public GraphicsAdapter(DrawingContext g, RRect initialClip, bool releaseGraphics = false) |
|
||||
: base(AvaloniaAdapter.Instance, initialClip) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(g, "g"); |
|
||||
|
|
||||
_g = g; |
|
||||
_releaseGraphics = releaseGraphics; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
public GraphicsAdapter() |
|
||||
: base(AvaloniaAdapter.Instance, RRect.Empty) |
|
||||
{ |
|
||||
_g = null; |
|
||||
_releaseGraphics = false; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
public override void PopClip() |
|
||||
{ |
|
||||
_clipStack.Pop()?.Dispose(); |
|
||||
} |
|
||||
|
|
||||
public override void PushClip(RRect rect) |
|
||||
{ |
|
||||
_clipStack.Push(_g.PushClip(Util.Convert(rect))); |
|
||||
//_clipStack.Push(rect);
|
|
||||
//_g.PushClip(new RectangleGeometry(Utils.Convert(rect)));
|
|
||||
} |
|
||||
|
|
||||
public override void PushClipExclude(RRect rect) |
|
||||
{ |
|
||||
_clipStack.Push(null); |
|
||||
|
|
||||
//TODO: Implement exclude rect, see #128
|
|
||||
//var geometry = new CombinedGeometry();
|
|
||||
//geometry.Geometry1 = new RectangleGeometry(Utils.Convert(_clipStack.Peek()));
|
|
||||
//geometry.Geometry2 = new RectangleGeometry(Utils.Convert(rect));
|
|
||||
//geometry.GeometryCombineMode = GeometryCombineMode.Exclude;
|
|
||||
|
|
||||
//_clipStack.Push(_clipStack.Peek());
|
|
||||
//_g.PushClip(geometry);
|
|
||||
} |
|
||||
|
|
||||
public override Object SetAntiAliasSmoothingMode() |
|
||||
{ |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
public override void ReturnPreviousSmoothingMode(Object prevMode) |
|
||||
{ } |
|
||||
|
|
||||
public override RSize MeasureString(string str, RFont font) |
|
||||
{ |
|
||||
var text = GetText(str, font); |
|
||||
var measure = text.Measure(); |
|
||||
return new RSize(measure.Width, measure.Height); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
FormattedText GetText(string str, RFont font) |
|
||||
{ |
|
||||
var f = ((FontAdapter)font); |
|
||||
return new FormattedText |
|
||||
{ |
|
||||
Text = str, |
|
||||
Typeface = new Typeface(f.Name, font.Size, f.FontStyle, f.Weight), |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth) |
|
||||
{ |
|
||||
var text = GetText(str, font); |
|
||||
var fullLength = text.Measure().Width; |
|
||||
if (fullLength < maxWidth) |
|
||||
{ |
|
||||
charFitWidth = fullLength; |
|
||||
charFit = str.Length; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int lastLen = 0; |
|
||||
double lastMeasure = 0; |
|
||||
BinarySearch(len => |
|
||||
{ |
|
||||
text = GetText(str.Substring(0, len), font); |
|
||||
var size = text.Measure().Width; |
|
||||
lastMeasure = size; |
|
||||
lastLen = len; |
|
||||
if (size <= maxWidth) |
|
||||
return -1; |
|
||||
return 1; |
|
||||
|
|
||||
}, 0, str.Length); |
|
||||
if (lastMeasure > maxWidth) |
|
||||
{ |
|
||||
lastLen--; |
|
||||
lastMeasure = GetText(str.Substring(0, lastLen), font).Measure().Width; |
|
||||
} |
|
||||
charFit = lastLen; |
|
||||
charFitWidth = lastMeasure; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
private static int BinarySearch(Func<int, int> condition, int start, int end) |
|
||||
{ |
|
||||
do |
|
||||
{ |
|
||||
int ind = start + (end - start)/2; |
|
||||
int res = condition(ind); |
|
||||
if (res == 0) |
|
||||
return ind; |
|
||||
else if (res > 0) |
|
||||
{ |
|
||||
if (start != ind) |
|
||||
start = ind; |
|
||||
else |
|
||||
start = ind + 1; |
|
||||
} |
|
||||
else |
|
||||
end = ind; |
|
||||
|
|
||||
} while (end > start); |
|
||||
return -1; |
|
||||
} |
|
||||
|
|
||||
public override void DrawString(string str, RFont font, RColor color, RPoint point, RSize size, bool rtl) |
|
||||
{ |
|
||||
var text = GetText(str, font); |
|
||||
text.Constraint = Util.Convert(size); |
|
||||
_g.DrawText(new SolidColorBrush(Util.Convert(color)), Util.Convert(point), text); |
|
||||
} |
|
||||
|
|
||||
public override RBrush GetTextureBrush(RImage image, RRect dstRect, RPoint translateTransformLocation) |
|
||||
{ |
|
||||
//TODO: Implement texture brush
|
|
||||
return AvaloniaAdapter.Instance.GetSolidBrush(Util.Convert(Colors.Magenta)); |
|
||||
|
|
||||
//var brush = new ImageBrush(((ImageAdapter)image).Image);
|
|
||||
//brush.Stretch = Stretch.None;
|
|
||||
//brush.TileMode = TileMode.Tile;
|
|
||||
//brush.Viewport = Utils.Convert(dstRect);
|
|
||||
//brush.ViewportUnits = BrushMappingMode.Absolute;
|
|
||||
//brush.Transform = new TranslateTransform(translateTransformLocation.X, translateTransformLocation.Y);
|
|
||||
//brush.Freeze();
|
|
||||
//return new BrushAdapter(brush);
|
|
||||
} |
|
||||
|
|
||||
public override RGraphicsPath GetGraphicsPath() |
|
||||
{ |
|
||||
return new GraphicsPathAdapter(); |
|
||||
} |
|
||||
|
|
||||
public override void Dispose() |
|
||||
{ |
|
||||
while (_clipStack.Count != 0) |
|
||||
PopClip(); |
|
||||
if (_releaseGraphics) |
|
||||
_g.Dispose(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
#region Delegate graphics methods
|
|
||||
|
|
||||
public override void DrawLine(RPen pen, double x1, double y1, double x2, double y2) |
|
||||
{ |
|
||||
x1 = (int)x1; |
|
||||
x2 = (int)x2; |
|
||||
y1 = (int)y1; |
|
||||
y2 = (int)y2; |
|
||||
|
|
||||
var adj = pen.Width; |
|
||||
if (Math.Abs(x1 - x2) < .1 && Math.Abs(adj % 2 - 1) < .1) |
|
||||
{ |
|
||||
x1 += .5; |
|
||||
x2 += .5; |
|
||||
} |
|
||||
if (Math.Abs(y1 - y2) < .1 && Math.Abs(adj % 2 - 1) < .1) |
|
||||
{ |
|
||||
y1 += .5; |
|
||||
y2 += .5; |
|
||||
} |
|
||||
|
|
||||
_g.DrawLine(((PenAdapter)pen).CreatePen(), new Point(x1, y1), new Point(x2, y2)); |
|
||||
} |
|
||||
|
|
||||
public override void DrawRectangle(RPen pen, double x, double y, double width, double height) |
|
||||
{ |
|
||||
var adj = pen.Width; |
|
||||
if (Math.Abs(adj % 2 - 1) < .1) |
|
||||
{ |
|
||||
x += .5; |
|
||||
y += .5; |
|
||||
} |
|
||||
_g.DrawRectangle(((PenAdapter) pen).CreatePen(), new Rect(x, y, width, height)); |
|
||||
} |
|
||||
|
|
||||
public override void DrawRectangle(RBrush brush, double x, double y, double width, double height) |
|
||||
{ |
|
||||
_g.FillRectangle(((BrushAdapter) brush).Brush, new Rect(x, y, width, height)); |
|
||||
} |
|
||||
|
|
||||
public override void DrawImage(RImage image, RRect destRect, RRect srcRect) |
|
||||
{ |
|
||||
_g.DrawImage(((ImageAdapter) image).Image, 1, Util.Convert(srcRect), Util.Convert(destRect)); |
|
||||
} |
|
||||
|
|
||||
public override void DrawImage(RImage image, RRect destRect) |
|
||||
{ |
|
||||
_g.DrawImage(((ImageAdapter) image).Image, 1, new Rect(0, 0, image.Width, image.Height), |
|
||||
Util.Convert(destRect)); |
|
||||
} |
|
||||
|
|
||||
public override void DrawPath(RPen pen, RGraphicsPath path) |
|
||||
{ |
|
||||
_g.DrawGeometry(null, ((PenAdapter)pen).CreatePen(), ((GraphicsPathAdapter)path).GetClosedGeometry()); |
|
||||
} |
|
||||
|
|
||||
public override void DrawPath(RBrush brush, RGraphicsPath path) |
|
||||
{ |
|
||||
_g.DrawGeometry(((BrushAdapter)brush).Brush, null, ((GraphicsPathAdapter)path).GetClosedGeometry()); |
|
||||
} |
|
||||
|
|
||||
public override void DrawPolygon(RBrush brush, RPoint[] points) |
|
||||
{ |
|
||||
if (points != null && points.Length > 0) |
|
||||
{ |
|
||||
var g = new StreamGeometry(); |
|
||||
using (var context = g.Open()) |
|
||||
{ |
|
||||
context.BeginFigure(Util.Convert(points[0]), true); |
|
||||
for (int i = 1; i < points.Length; i++) |
|
||||
context.LineTo(Util.Convert(points[i])); |
|
||||
context.EndFigure(false); |
|
||||
} |
|
||||
|
|
||||
_g.DrawGeometry(((BrushAdapter)brush).Brush, null, g); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
#endregion
|
|
||||
} |
|
||||
} |
|
||||
@ -1,67 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using Avalonia; |
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia graphics path object for core.
|
|
||||
/// </summary>
|
|
||||
internal sealed class GraphicsPathAdapter : RGraphicsPath |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The actual Avalonia graphics geometry instance.
|
|
||||
/// </summary>
|
|
||||
private readonly StreamGeometry _geometry = new StreamGeometry(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The context used in Avalonia geometry to render path
|
|
||||
/// </summary>
|
|
||||
private readonly StreamGeometryContext _geometryContext; |
|
||||
|
|
||||
public GraphicsPathAdapter() |
|
||||
{ |
|
||||
_geometryContext = _geometry.Open(); |
|
||||
} |
|
||||
|
|
||||
public override void Start(double x, double y) |
|
||||
{ |
|
||||
_geometryContext.BeginFigure(new Point(x, y), true); |
|
||||
} |
|
||||
|
|
||||
public override void LineTo(double x, double y) |
|
||||
{ |
|
||||
_geometryContext.LineTo(new Point(x, y)); |
|
||||
} |
|
||||
|
|
||||
public override void ArcTo(double x, double y, double size, Corner corner) |
|
||||
{ |
|
||||
_geometryContext.ArcTo(new Point(x, y), new Size(size, size), 0, false, SweepDirection.Clockwise); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Close the geometry to so no more path adding is allowed and return the instance so it can be rendered.
|
|
||||
/// </summary>
|
|
||||
public StreamGeometry GetClosedGeometry() |
|
||||
{ |
|
||||
_geometryContext.EndFigure(true); |
|
||||
_geometryContext.Dispose(); |
|
||||
return _geometry; |
|
||||
} |
|
||||
|
|
||||
public override void Dispose() |
|
||||
{ } |
|
||||
} |
|
||||
} |
|
||||
@ -1,52 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using Avalonia.Media.Imaging; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia Image object for core.
|
|
||||
/// </summary>
|
|
||||
internal sealed class ImageAdapter : RImage |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// the underline Avalonia image.
|
|
||||
/// </summary>
|
|
||||
private readonly Bitmap _image; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
public ImageAdapter(Bitmap image) |
|
||||
{ |
|
||||
_image = image; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// the underline Avalonia image.
|
|
||||
/// </summary>
|
|
||||
public Bitmap Image => _image; |
|
||||
|
|
||||
public override double Width => _image.PixelWidth; |
|
||||
|
|
||||
public override double Height => _image.PixelHeight; |
|
||||
|
|
||||
public override void Dispose() |
|
||||
{ |
|
||||
//TODO: Implement image disposal
|
|
||||
/*if (_image.StreamSource != null) |
|
||||
_image.StreamSource.Dispose();*/ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,79 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adapter for Avalonia pens objects for core.
|
|
||||
/// </summary>
|
|
||||
internal sealed class PenAdapter : RPen |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The actual Avalonia brush instance.
|
|
||||
/// </summary>
|
|
||||
private readonly IBrush _brush; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// the width of the pen
|
|
||||
/// </summary>
|
|
||||
private double _width; |
|
||||
|
|
||||
private DashStyle _dashStyle; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// the dash style of the pen
|
|
||||
/// </summary>
|
|
||||
//private DashStyle _dashStyle = DashStyles.Solid;
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
public PenAdapter(IBrush brush) |
|
||||
{ |
|
||||
_brush = brush; |
|
||||
} |
|
||||
|
|
||||
public override double Width |
|
||||
{ |
|
||||
get { return _width; } |
|
||||
set { _width = value; } |
|
||||
} |
|
||||
|
|
||||
public override RDashStyle DashStyle |
|
||||
{ |
|
||||
set { DashStyles.TryGetValue(value, out _dashStyle); } |
|
||||
} |
|
||||
|
|
||||
private static readonly Dictionary<RDashStyle, DashStyle> DashStyles = new Dictionary<RDashStyle, DashStyle> |
|
||||
{ |
|
||||
{RDashStyle.Solid,null }, |
|
||||
{RDashStyle.Dash, global::Avalonia.Media.DashStyle.Dash }, |
|
||||
{RDashStyle.DashDot, global::Avalonia.Media.DashStyle.DashDot }, |
|
||||
{RDashStyle.DashDotDot, global::Avalonia.Media.DashStyle.DashDotDot }, |
|
||||
{RDashStyle.Dot, global::Avalonia.Media.DashStyle.Dot } |
|
||||
}; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Create the actual Avalonia pen instance.
|
|
||||
/// </summary>
|
|
||||
public Pen CreatePen() |
|
||||
{ |
|
||||
var pen = new Pen(_brush, _width, _dashStyle); |
|
||||
return pen; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,24 +0,0 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
|
||||
<PropertyGroup> |
|
||||
<TargetFramework>netstandard2.0</TargetFramework> |
|
||||
<EnableDefaultCompileItems>False</EnableDefaultCompileItems> |
|
||||
<EnableDefaultItems>False</EnableDefaultItems> |
|
||||
<NoWarn>CS0436</NoWarn> |
|
||||
</PropertyGroup> |
|
||||
<ItemGroup> |
|
||||
<Content Include="external\Source\HtmlRenderer\Core\Utils\ImageError.png" /> |
|
||||
<Content Include="external\Source\HtmlRenderer\Core\Utils\ImageLoad.png" /> |
|
||||
</ItemGroup> |
|
||||
<ItemGroup> |
|
||||
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" /> |
|
||||
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> |
|
||||
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|
||||
<ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" /> |
|
||||
<ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
|
||||
<ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" /> |
|
||||
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
|
||||
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" /> |
|
||||
</ItemGroup> |
|
||||
|
|
||||
<Import Project="..\..\build\Rx.props" /> |
|
||||
</Project> |
|
||||
@ -1,2 +0,0 @@ |
|||||
<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:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp60</s:String></wpf:ResourceDictionary> |
|
||||
@ -1,16 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Text; |
|
||||
|
|
||||
namespace System.Net |
|
||||
{ |
|
||||
class WebException : Exception |
|
||||
{ |
|
||||
public object Response { get; set; } |
|
||||
} |
|
||||
|
|
||||
class HttpWebResponse |
|
||||
{ |
|
||||
public HttpStatusCode StatusCode { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,28 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
internal class CategoryAttribute : Attribute |
|
||||
{ |
|
||||
public CategoryAttribute(string s) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
internal class DescriptionAttribute : Attribute |
|
||||
{ |
|
||||
public DescriptionAttribute(string s) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
internal class BrowsableAttribute : Attribute |
|
||||
{ |
|
||||
public BrowsableAttribute(bool b) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,22 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
using TheArtOfDev.HtmlRenderer.Core.Handlers; |
|
||||
|
|
||||
namespace System.Threading |
|
||||
{ |
|
||||
class ThreadPool |
|
||||
{ |
|
||||
public static void QueueUserWorkItem(Action<object> cb, object state) |
|
||||
{ |
|
||||
Task.Factory.StartNew(() => cb(state)); |
|
||||
} |
|
||||
|
|
||||
public static void QueueUserWorkItem(Action<object> cb) |
|
||||
{ |
|
||||
Task.Factory.StartNew(() => cb(null)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,471 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using Avalonia; |
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Controls.Html; |
|
||||
using Avalonia.Input; |
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
using TheArtOfDev.HtmlRenderer.Core; |
|
||||
using TheArtOfDev.HtmlRenderer.Core.Entities; |
|
||||
using TheArtOfDev.HtmlRenderer.Core.Parse; |
|
||||
using TheArtOfDev.HtmlRenderer.Core.Utils; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia.Adapters; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Low level handling of Html Renderer logic, this class is used by <see cref="HtmlParser"/>,
|
|
||||
/// <see cref="HtmlLabel"/>, <see cref="HtmlToolTip"/> and <see cref="HtmlRender"/>.<br/>
|
|
||||
/// </summary>
|
|
||||
/// <seealso cref="HtmlContainerInt"/>
|
|
||||
public sealed class HtmlContainer : IDisposable |
|
||||
{ |
|
||||
#region Fields and Consts
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The internal core html container
|
|
||||
/// </summary>
|
|
||||
private readonly HtmlContainerInt _htmlContainerInt; |
|
||||
|
|
||||
#endregion
|
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
public HtmlContainer() |
|
||||
{ |
|
||||
_htmlContainerInt = new HtmlContainerInt(AvaloniaAdapter.Instance); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when the set html document has been fully loaded.<br/>
|
|
||||
/// Allows manipulation of the html dom, scroll position, etc.
|
|
||||
/// </summary>
|
|
||||
public event EventHandler LoadComplete |
|
||||
{ |
|
||||
add { _htmlContainerInt.LoadComplete += value; } |
|
||||
remove { _htmlContainerInt.LoadComplete -= value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when the user clicks on a link in the html.<br/>
|
|
||||
/// Allows canceling the execution of the link.
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlLinkClickedEventArgs> LinkClicked |
|
||||
{ |
|
||||
add { _htmlContainerInt.LinkClicked += value; } |
|
||||
remove { _htmlContainerInt.LinkClicked -= value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when html renderer requires refresh of the control hosting (invalidation and re-layout).
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread.
|
|
||||
/// </remarks>
|
|
||||
public event EventHandler<HtmlRefreshEventArgs> Refresh |
|
||||
{ |
|
||||
add { _htmlContainerInt.Refresh += value; } |
|
||||
remove { _htmlContainerInt.Refresh -= value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when Html Renderer request scroll to specific location.<br/>
|
|
||||
/// This can occur on document anchor click.
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlScrollEventArgs> ScrollChange |
|
||||
{ |
|
||||
add { _htmlContainerInt.ScrollChange += value; } |
|
||||
remove { _htmlContainerInt.ScrollChange -= value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when an error occurred during html rendering.<br/>
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread.
|
|
||||
/// </remarks>
|
|
||||
public event EventHandler<HtmlRenderErrorEventArgs> RenderError |
|
||||
{ |
|
||||
add { _htmlContainerInt.RenderError += value; } |
|
||||
remove { _htmlContainerInt.RenderError -= value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when a stylesheet is about to be loaded by file path or URI by link element.<br/>
|
|
||||
/// This event allows to provide the stylesheet manually or provide new source (file or Uri) to load from.<br/>
|
|
||||
/// If no alternative data is provided the original source will be used.<br/>
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlStylesheetLoadEventArgs> StylesheetLoad |
|
||||
{ |
|
||||
add { _htmlContainerInt.StylesheetLoad += value; } |
|
||||
remove { _htmlContainerInt.StylesheetLoad -= value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when an image is about to be loaded by file path or URI.<br/>
|
|
||||
/// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI.
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlImageLoadEventArgs> ImageLoad |
|
||||
{ |
|
||||
add { _htmlContainerInt.ImageLoad += value; } |
|
||||
remove { _htmlContainerInt.ImageLoad -= value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The internal core html container
|
|
||||
/// </summary>
|
|
||||
internal HtmlContainerInt HtmlContainerInt |
|
||||
{ |
|
||||
get { return _htmlContainerInt; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// the parsed stylesheet data used for handling the html
|
|
||||
/// </summary>
|
|
||||
public CssData CssData |
|
||||
{ |
|
||||
get { return _htmlContainerInt.CssData; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets a value indicating if image asynchronous loading should be avoided (default - false).<br/>
|
|
||||
/// True - images are loaded synchronously during html parsing.<br/>
|
|
||||
/// False - images are loaded asynchronously to html parsing when downloaded from URL or loaded from disk.<br/>
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Asynchronously image loading allows to unblock html rendering while image is downloaded or loaded from disk using IO
|
|
||||
/// ports to achieve better performance.<br/>
|
|
||||
/// Asynchronously image loading should be avoided when the full html content must be available during render, like render to image.
|
|
||||
/// </remarks>
|
|
||||
public bool AvoidAsyncImagesLoading |
|
||||
{ |
|
||||
get { return _htmlContainerInt.AvoidAsyncImagesLoading; } |
|
||||
set { _htmlContainerInt.AvoidAsyncImagesLoading = value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).<br/>
|
|
||||
/// True - images are loaded as soon as the html is parsed.<br/>
|
|
||||
/// False - images that are not visible because of scroll location are not loaded until they are scrolled to.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Images late loading improve performance if the page contains image outside the visible scroll area, especially if there is large
|
|
||||
/// amount of images, as all image loading is delayed (downloading and loading into memory).<br/>
|
|
||||
/// Late image loading may effect the layout and actual size as image without set size will not have actual size until they are loaded
|
|
||||
/// resulting in layout change during user scroll.<br/>
|
|
||||
/// Early image loading may also effect the layout if image without known size above the current scroll location are loaded as they
|
|
||||
/// will push the html elements down.
|
|
||||
/// </remarks>
|
|
||||
public bool AvoidImagesLateLoading |
|
||||
{ |
|
||||
get { return _htmlContainerInt.AvoidImagesLateLoading; } |
|
||||
set { _htmlContainerInt.AvoidImagesLateLoading = value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Is content selection is enabled for the rendered html (default - true).<br/>
|
|
||||
/// If set to 'false' the rendered html will be static only with ability to click on links.
|
|
||||
/// </summary>
|
|
||||
public bool IsSelectionEnabled |
|
||||
{ |
|
||||
get { return _htmlContainerInt.IsSelectionEnabled; } |
|
||||
set { _htmlContainerInt.IsSelectionEnabled = value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Is the build-in context menu enabled and will be shown on mouse right click (default - true)
|
|
||||
/// </summary>
|
|
||||
public bool IsContextMenuEnabled |
|
||||
{ |
|
||||
get { return _htmlContainerInt.IsContextMenuEnabled; } |
|
||||
set { _htmlContainerInt.IsContextMenuEnabled = value; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The scroll offset of the html.<br/>
|
|
||||
/// This will adjust the rendered html by the given offset so the content will be "scrolled".<br/>
|
|
||||
/// </summary>
|
|
||||
/// <example>
|
|
||||
/// Element that is rendered at location (50,100) with offset of (0,200) will not be rendered as it
|
|
||||
/// will be at -100 therefore outside the client Rect.
|
|
||||
/// </example>
|
|
||||
public Point ScrollOffset |
|
||||
{ |
|
||||
get { return Util.Convert(_htmlContainerInt.ScrollOffset); } |
|
||||
set { _htmlContainerInt.ScrollOffset = Util.Convert(value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The top-left most location of the rendered html.<br/>
|
|
||||
/// This will offset the top-left corner of the rendered html.
|
|
||||
/// </summary>
|
|
||||
public Point Location |
|
||||
{ |
|
||||
get { return Util.Convert(_htmlContainerInt.Location); } |
|
||||
set { _htmlContainerInt.Location = Util.Convert(value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The max width and height of the rendered html.<br/>
|
|
||||
/// The max width will effect the html layout wrapping lines, resize images and tables where possible.<br/>
|
|
||||
/// The max height does NOT effect layout, but will not render outside it (clip).<br/>
|
|
||||
/// <see cref="ActualSize"/> can be exceed the max size by layout restrictions (unwrappable line, set image size, etc.).<br/>
|
|
||||
/// Set zero for unlimited (width\height separately).<br/>
|
|
||||
/// </summary>
|
|
||||
public Size MaxSize |
|
||||
{ |
|
||||
get { return Util.Convert(_htmlContainerInt.MaxSize); } |
|
||||
set { _htmlContainerInt.MaxSize = Util.Convert(value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The actual size of the rendered html (after layout)
|
|
||||
/// </summary>
|
|
||||
public Size ActualSize |
|
||||
{ |
|
||||
get { return Util.Convert(_htmlContainerInt.ActualSize); } |
|
||||
internal set { _htmlContainerInt.ActualSize = Util.Convert(value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the currently selected text segment in the html.
|
|
||||
/// </summary>
|
|
||||
public string SelectedText |
|
||||
{ |
|
||||
get { return _htmlContainerInt.SelectedText; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Copy the currently selected html segment with style.
|
|
||||
/// </summary>
|
|
||||
public string SelectedHtml |
|
||||
{ |
|
||||
get { return _htmlContainerInt.SelectedHtml; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Clear the current selection.
|
|
||||
/// </summary>
|
|
||||
public void ClearSelection() |
|
||||
{ |
|
||||
HtmlContainerInt.ClearSelection(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init with optional document and stylesheet.
|
|
||||
/// </summary>
|
|
||||
/// <param name="htmlSource">the html to init with, init empty if not given</param>
|
|
||||
/// <param name="baseCssData">optional: the stylesheet to init with, init default if not given</param>
|
|
||||
public void SetHtml(string htmlSource, CssData baseCssData = null) |
|
||||
{ |
|
||||
_htmlContainerInt.SetHtml(htmlSource, baseCssData); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Clear the content of the HTML container releasing any resources used to render previously existing content.
|
|
||||
/// </summary>
|
|
||||
public void Clear() |
|
||||
{ |
|
||||
_htmlContainerInt.Clear(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get html from the current DOM tree with style if requested.
|
|
||||
/// </summary>
|
|
||||
/// <param name="styleGen">Optional: controls the way styles are generated when html is generated (default: <see cref="HtmlGenerationStyle.Inline"/>)</param>
|
|
||||
/// <returns>generated html</returns>
|
|
||||
public string GetHtml(HtmlGenerationStyle styleGen = HtmlGenerationStyle.Inline) |
|
||||
{ |
|
||||
return _htmlContainerInt.GetHtml(styleGen); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get attribute value of element at the given x,y location by given key.<br/>
|
|
||||
/// If more than one element exist with the attribute at the location the inner most is returned.
|
|
||||
/// </summary>
|
|
||||
/// <param name="location">the location to find the attribute at</param>
|
|
||||
/// <param name="attribute">the attribute key to get value by</param>
|
|
||||
/// <returns>found attribute value or null if not found</returns>
|
|
||||
public string GetAttributeAt(Point location, string attribute) |
|
||||
{ |
|
||||
return _htmlContainerInt.GetAttributeAt(Util.Convert(location), attribute); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get all the links in the HTML with the element Rect and href data.
|
|
||||
/// </summary>
|
|
||||
/// <returns>collection of all the links in the HTML</returns>
|
|
||||
public List<LinkElementData<Rect>> GetLinks() |
|
||||
{ |
|
||||
var linkElements = new List<LinkElementData<Rect>>(); |
|
||||
foreach (var link in HtmlContainerInt.GetLinks()) |
|
||||
{ |
|
||||
linkElements.Add(new LinkElementData<Rect>(link.Id, link.Href, Util.Convert(link.Rectangle))); |
|
||||
} |
|
||||
return linkElements; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get css link href at the given x,y location.
|
|
||||
/// </summary>
|
|
||||
/// <param name="location">the location to find the link at</param>
|
|
||||
/// <returns>css link href if exists or null</returns>
|
|
||||
public string GetLinkAt(Point location) |
|
||||
{ |
|
||||
return _htmlContainerInt.GetLinkAt(Util.Convert(location)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the Rect of html element as calculated by html layout.<br/>
|
|
||||
/// Element if found by id (id attribute on the html element).<br/>
|
|
||||
/// Note: to get the screen Rect you need to adjust by the hosting control.<br/>
|
|
||||
/// </summary>
|
|
||||
/// <param name="elementId">the id of the element to get its Rect</param>
|
|
||||
/// <returns>the Rect of the element or null if not found</returns>
|
|
||||
public Rect? GetElementRectangle(string elementId) |
|
||||
{ |
|
||||
var r = _htmlContainerInt.GetElementRectangle(elementId); |
|
||||
return r.HasValue ? Util.Convert(r.Value) : (Rect?)null; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Measures the bounds of box and children, recursively.
|
|
||||
/// </summary>
|
|
||||
public void PerformLayout() |
|
||||
{ |
|
||||
using (var ig = new GraphicsAdapter()) |
|
||||
{ |
|
||||
_htmlContainerInt.PerformLayout(ig); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Render the html using the given device.
|
|
||||
/// </summary>
|
|
||||
/// <param name="g">the device to use to render</param>
|
|
||||
/// <param name="clip">the clip rectangle of the html container</param>
|
|
||||
public void PerformPaint(DrawingContext g, Rect clip) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(g, "g"); |
|
||||
|
|
||||
using (var ig = new GraphicsAdapter(g, Util.Convert(clip))) |
|
||||
{ |
|
||||
_htmlContainerInt.PerformPaint(ig); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse down to handle selection.
|
|
||||
/// </summary>
|
|
||||
/// <param name="parent">the control hosting the html to invalidate</param>
|
|
||||
/// <param name="e">the mouse event args</param>
|
|
||||
public void HandleLeftMouseDown(Control parent, PointerEventArgs e) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(parent, "parent"); |
|
||||
ArgChecker.AssertArgNotNull(e, "e"); |
|
||||
|
|
||||
_htmlContainerInt.HandleMouseDown(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent))); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse up to handle selection and link click.
|
|
||||
/// </summary>
|
|
||||
/// <param name="parent">the control hosting the html to invalidate</param>
|
|
||||
/// <param name="e">the mouse event args</param>
|
|
||||
public void HandleLeftMouseUp(Control parent, PointerEventArgs e) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(parent, "parent"); |
|
||||
ArgChecker.AssertArgNotNull(e, "e"); |
|
||||
|
|
||||
var mouseEvent = new RMouseEvent(true); |
|
||||
_htmlContainerInt.HandleMouseUp(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent)), mouseEvent); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse double click to select word under the mouse.
|
|
||||
/// </summary>
|
|
||||
/// <param name="parent">the control hosting the html to set cursor and invalidate</param>
|
|
||||
/// <param name="e">mouse event args</param>
|
|
||||
public void HandleMouseDoubleClick(Control parent, PointerEventArgs e) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(parent, "parent"); |
|
||||
ArgChecker.AssertArgNotNull(e, "e"); |
|
||||
|
|
||||
_htmlContainerInt.HandleMouseDoubleClick(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent))); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse move to handle hover cursor and text selection.
|
|
||||
/// </summary>
|
|
||||
/// <param name="parent">the control hosting the html to set cursor and invalidate</param>
|
|
||||
/// <param name="mousePos">the mouse event args</param>
|
|
||||
public void HandleMouseMove(Control parent, Point mousePos) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(parent, "parent"); |
|
||||
|
|
||||
_htmlContainerInt.HandleMouseMove(new ControlAdapter(parent), Util.Convert(mousePos)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse leave to handle hover cursor.
|
|
||||
/// </summary>
|
|
||||
/// <param name="parent">the control hosting the html to set cursor and invalidate</param>
|
|
||||
public void HandleMouseLeave(Control parent) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(parent, "parent"); |
|
||||
|
|
||||
_htmlContainerInt.HandleMouseLeave(new ControlAdapter(parent)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle key down event for selection and copy.
|
|
||||
/// </summary>
|
|
||||
/// <param name="parent">the control hosting the html to invalidate</param>
|
|
||||
/// <param name="e">the pressed key</param>
|
|
||||
public void HandleKeyDown(Control parent, KeyEventArgs e) |
|
||||
{ |
|
||||
ArgChecker.AssertArgNotNull(parent, "parent"); |
|
||||
ArgChecker.AssertArgNotNull(e, "e"); |
|
||||
|
|
||||
_htmlContainerInt.HandleKeyDown(new ControlAdapter(parent), CreateKeyEevent(e)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
||||
/// </summary>
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
_htmlContainerInt.Dispose(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
#region Private methods
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Create HtmlRenderer key event from Avalonia key event.
|
|
||||
/// </summary>
|
|
||||
private static RKeyEvent CreateKeyEevent(KeyEventArgs e) |
|
||||
{ |
|
||||
var control = (e.Modifiers & InputModifiers.Control) == InputModifiers.Control; |
|
||||
return new RKeyEvent(control, e.Key == Key.A, e.Key == Key.C); |
|
||||
} |
|
||||
|
|
||||
#endregion
|
|
||||
} |
|
||||
} |
|
||||
@ -1,616 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using System; |
|
||||
using Avalonia.Controls.Primitives; |
|
||||
using Avalonia.HtmlRenderer; |
|
||||
using Avalonia.Input; |
|
||||
using Avalonia.Interactivity; |
|
||||
using Avalonia.Media; |
|
||||
using Avalonia.Threading; |
|
||||
using Avalonia.VisualTree; |
|
||||
using TheArtOfDev.HtmlRenderer.Core; |
|
||||
using TheArtOfDev.HtmlRenderer.Core.Entities; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia.Adapters; |
|
||||
|
|
||||
namespace Avalonia.Controls.Html |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides HTML rendering using the text property.<br/>
|
|
||||
/// Avalonia control that will render html content in it's client rectangle.<br/>
|
|
||||
/// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.<br/>
|
|
||||
/// <para>
|
|
||||
/// The major differential to use HtmlPanel or HtmlLabel is size and scrollbars.<br/>
|
|
||||
/// If the size of the control depends on the html content the HtmlLabel should be used.<br/>
|
|
||||
/// If the size is set by some kind of layout then HtmlPanel is more suitable, also shows scrollbars if the html contents is larger than the control client rectangle.<br/>
|
|
||||
/// </para>
|
|
||||
/// <para>
|
|
||||
/// <h4>LinkClicked event:</h4>
|
|
||||
/// Raised when the user clicks on a link in the html.<br/>
|
|
||||
/// Allows canceling the execution of the link.
|
|
||||
/// </para>
|
|
||||
/// <para>
|
|
||||
/// <h4>StylesheetLoad event:</h4>
|
|
||||
/// Raised when a stylesheet is about to be loaded by file path or URI by link element.<br/>
|
|
||||
/// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.<br/>
|
|
||||
/// If no alternative data is provided the original source will be used.<br/>
|
|
||||
/// </para>
|
|
||||
/// <para>
|
|
||||
/// <h4>ImageLoad event:</h4>
|
|
||||
/// Raised when an image is about to be loaded by file path or URI.<br/>
|
|
||||
/// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI.
|
|
||||
/// </para>
|
|
||||
/// <para>
|
|
||||
/// <h4>RenderError event:</h4>
|
|
||||
/// Raised when an error occurred during html rendering.<br/>
|
|
||||
/// </para>
|
|
||||
/// </summary>
|
|
||||
|
|
||||
public class HtmlControl : Control |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Underline html container instance.
|
|
||||
/// </summary>
|
|
||||
protected readonly HtmlContainer _htmlContainer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// the base stylesheet data used in the control
|
|
||||
/// </summary>
|
|
||||
protected CssData _baseCssData; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The last position of the scrollbars to know if it has changed to update mouse
|
|
||||
/// </summary>
|
|
||||
protected Point _lastScrollOffset; |
|
||||
|
|
||||
public static readonly AvaloniaProperty AvoidImagesLateLoadingProperty = |
|
||||
PropertyHelper.Register<HtmlControl, bool>(nameof(AvoidImagesLateLoading), false, OnAvaloniaProperty_valueChanged); |
|
||||
public static readonly AvaloniaProperty IsSelectionEnabledProperty = |
|
||||
PropertyHelper.Register<HtmlControl, bool>(nameof(IsSelectionEnabled), true, OnAvaloniaProperty_valueChanged); |
|
||||
public static readonly AvaloniaProperty IsContextMenuEnabledProperty = |
|
||||
PropertyHelper.Register<HtmlControl, bool>(nameof(IsContextMenuEnabled), true, OnAvaloniaProperty_valueChanged); |
|
||||
|
|
||||
public static readonly AvaloniaProperty BaseStylesheetProperty = |
|
||||
PropertyHelper.Register<HtmlControl, string>(nameof(BaseStylesheet), null, OnAvaloniaProperty_valueChanged); |
|
||||
|
|
||||
public static readonly AvaloniaProperty TextProperty = |
|
||||
PropertyHelper.Register<HtmlControl, string>(nameof(Text), null, OnAvaloniaProperty_valueChanged); |
|
||||
|
|
||||
public static readonly StyledProperty<IBrush> BackgroundProperty = |
|
||||
Border.BackgroundProperty.AddOwner<HtmlControl>(); |
|
||||
|
|
||||
public static readonly AvaloniaProperty BorderThicknessProperty = |
|
||||
AvaloniaProperty.Register<HtmlControl, Thickness>(nameof(BorderThickness), new Thickness(0)); |
|
||||
|
|
||||
public static readonly AvaloniaProperty BorderBrushProperty = |
|
||||
AvaloniaProperty.Register<HtmlControl, IBrush>(nameof(BorderBrush)); |
|
||||
|
|
||||
public static readonly AvaloniaProperty PaddingProperty = |
|
||||
AvaloniaProperty.Register<HtmlControl, Thickness>(nameof(Padding), new Thickness(0)); |
|
||||
|
|
||||
public static readonly RoutedEvent LoadCompleteEvent = |
|
||||
RoutedEvent.Register<RoutedEventArgs>("LoadComplete", RoutingStrategies.Bubble, typeof(HtmlControl)); |
|
||||
public static readonly RoutedEvent LinkClickedEvent = |
|
||||
RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlLinkClickedEventArgs>>("LinkClicked", RoutingStrategies.Bubble, typeof(HtmlControl)); |
|
||||
public static readonly RoutedEvent RenderErrorEvent |
|
||||
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlRenderErrorEventArgs>>("RenderError", RoutingStrategies.Bubble, typeof(HtmlControl)); |
|
||||
public static readonly RoutedEvent RefreshEvent |
|
||||
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlRefreshEventArgs>>("Refresh", RoutingStrategies.Bubble, typeof(HtmlControl)); |
|
||||
public static readonly RoutedEvent StylesheetLoadEvent |
|
||||
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlStylesheetLoadEventArgs>>("StylesheetLoad", RoutingStrategies.Bubble, typeof(HtmlControl)); |
|
||||
|
|
||||
public static readonly RoutedEvent ImageLoadEvent |
|
||||
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlImageLoadEventArgs>>("ImageLoad", RoutingStrategies.Bubble, |
|
||||
typeof (HtmlControl)); |
|
||||
|
|
||||
static HtmlControl() |
|
||||
{ |
|
||||
FocusableProperty.OverrideDefaultValue(typeof(HtmlControl), true); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Creates a new HtmlPanel and sets a basic css for it's styling.
|
|
||||
/// </summary>
|
|
||||
protected HtmlControl() |
|
||||
{ |
|
||||
_htmlContainer = new HtmlContainer(); |
|
||||
_htmlContainer.LoadComplete += (_, e) => OnLoadComplete(e); |
|
||||
_htmlContainer.LinkClicked += (_, e) => OnLinkClicked(e); |
|
||||
_htmlContainer.RenderError += (_, e) => OnRenderError(e); |
|
||||
_htmlContainer.Refresh += (_, e) => OnRefresh(e); |
|
||||
_htmlContainer.StylesheetLoad += (_, e) => OnStylesheetLoad(e); |
|
||||
_htmlContainer.ImageLoad += (_, e) => OnImageLoad(e); |
|
||||
} |
|
||||
|
|
||||
//Hack for adapter
|
|
||||
internal bool LeftMouseButton { get; private set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when the set html document has been fully loaded.<br/>
|
|
||||
/// Allows manipulation of the html dom, scroll position, etc.
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlRendererRoutedEventArgs<EventArgs>> LoadComplete |
|
||||
{ |
|
||||
add { AddHandler(LoadCompleteEvent, value); } |
|
||||
remove { RemoveHandler(LoadCompleteEvent, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when the user clicks on a link in the html.<br/>
|
|
||||
/// Allows canceling the execution of the link.
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlLinkClickedEventArgs>> LinkClicked |
|
||||
{ |
|
||||
add { AddHandler(LinkClickedEvent, value); } |
|
||||
remove { RemoveHandler(LinkClickedEvent, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when an error occurred during html rendering.<br/>
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlRenderErrorEventArgs>> RenderError |
|
||||
{ |
|
||||
add { AddHandler(RenderErrorEvent, value); } |
|
||||
remove { RemoveHandler(RenderErrorEvent, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when a stylesheet is about to be loaded by file path or URI by link element.<br/>
|
|
||||
/// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.<br/>
|
|
||||
/// If no alternative data is provided the original source will be used.<br/>
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlStylesheetLoadEventArgs>> StylesheetLoad |
|
||||
{ |
|
||||
add { AddHandler(StylesheetLoadEvent, value); } |
|
||||
remove { RemoveHandler(StylesheetLoadEvent, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Raised when an image is about to be loaded by file path or URI.<br/>
|
|
||||
/// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI.
|
|
||||
/// </summary>
|
|
||||
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlImageLoadEventArgs>> ImageLoad |
|
||||
{ |
|
||||
add { AddHandler(ImageLoadEvent, value); } |
|
||||
remove { RemoveHandler(ImageLoadEvent, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).<br/>
|
|
||||
/// True - images are loaded as soon as the html is parsed.<br/>
|
|
||||
/// False - images that are not visible because of scroll location are not loaded until they are scrolled to.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Images late loading improve performance if the page contains image outside the visible scroll area, especially if there is large
|
|
||||
/// amount of images, as all image loading is delayed (downloading and loading into memory).<br/>
|
|
||||
/// Late image loading may effect the layout and actual size as image without set size will not have actual size until they are loaded
|
|
||||
/// resulting in layout change during user scroll.<br/>
|
|
||||
/// Early image loading may also effect the layout if image without known size above the current scroll location are loaded as they
|
|
||||
/// will push the html elements down.
|
|
||||
/// </remarks>
|
|
||||
[Category("Behavior")] |
|
||||
[Description("If image loading only when visible should be avoided")] |
|
||||
public bool AvoidImagesLateLoading |
|
||||
{ |
|
||||
get { return (bool)GetValue(AvoidImagesLateLoadingProperty); } |
|
||||
set { SetValue(AvoidImagesLateLoadingProperty, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Is content selection is enabled for the rendered html (default - true).<br/>
|
|
||||
/// If set to 'false' the rendered html will be static only with ability to click on links.
|
|
||||
/// </summary>
|
|
||||
[Category("Behavior")] |
|
||||
[Description("Is content selection is enabled for the rendered html.")] |
|
||||
public bool IsSelectionEnabled |
|
||||
{ |
|
||||
get { return (bool)GetValue(IsSelectionEnabledProperty); } |
|
||||
set { SetValue(IsSelectionEnabledProperty, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Is the build-in context menu enabled and will be shown on mouse right click (default - true)
|
|
||||
/// </summary>
|
|
||||
[Category("Behavior")] |
|
||||
[Description("Is the build-in context menu enabled and will be shown on mouse right click.")] |
|
||||
public bool IsContextMenuEnabled |
|
||||
{ |
|
||||
get { return (bool)GetValue(IsContextMenuEnabledProperty); } |
|
||||
set { SetValue(IsContextMenuEnabledProperty, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Set base stylesheet to be used by html rendered in the panel.
|
|
||||
/// </summary>
|
|
||||
[Category("Appearance")] |
|
||||
[Description("Set base stylesheet to be used by html rendered in the control.")] |
|
||||
public string BaseStylesheet |
|
||||
{ |
|
||||
get { return (string)GetValue(BaseStylesheetProperty); } |
|
||||
set { SetValue(BaseStylesheetProperty, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the text of this panel
|
|
||||
/// </summary>
|
|
||||
[Description("Sets the html of this control.")] |
|
||||
public string Text |
|
||||
{ |
|
||||
get { return (string)GetValue(TextProperty); } |
|
||||
set { SetValue(TextProperty, value); } |
|
||||
} |
|
||||
|
|
||||
public Thickness BorderThickness |
|
||||
{ |
|
||||
get { return (Thickness) GetValue(BorderThicknessProperty); } |
|
||||
set { SetValue(BorderThicknessProperty, value); } |
|
||||
} |
|
||||
|
|
||||
public IBrush BorderBrush |
|
||||
{ |
|
||||
get { return (IBrush)GetValue(BorderBrushProperty); } |
|
||||
set { SetValue(BorderThicknessProperty, value); } |
|
||||
} |
|
||||
|
|
||||
public Thickness Padding |
|
||||
{ |
|
||||
get { return (Thickness)GetValue(PaddingProperty); } |
|
||||
set { SetValue(PaddingProperty, value); } |
|
||||
} |
|
||||
|
|
||||
public IBrush Background |
|
||||
{ |
|
||||
get { return (IBrush) GetValue(BackgroundProperty); } |
|
||||
set { SetValue(BackgroundProperty, value);} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the currently selected text segment in the html.
|
|
||||
/// </summary>
|
|
||||
[Browsable(false)] |
|
||||
public virtual string SelectedText |
|
||||
{ |
|
||||
get { return _htmlContainer.SelectedText; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Copy the currently selected html segment with style.
|
|
||||
/// </summary>
|
|
||||
[Browsable(false)] |
|
||||
public virtual string SelectedHtml |
|
||||
{ |
|
||||
get { return _htmlContainer.SelectedHtml; } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get html from the current DOM tree with inline style.
|
|
||||
/// </summary>
|
|
||||
/// <returns>generated html</returns>
|
|
||||
public virtual string GetHtml() |
|
||||
{ |
|
||||
return _htmlContainer != null ? _htmlContainer.GetHtml() : null; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the rectangle of html element as calculated by html layout.<br/>
|
|
||||
/// Element if found by id (id attribute on the html element).<br/>
|
|
||||
/// Note: to get the screen rectangle you need to adjust by the hosting control.<br/>
|
|
||||
/// </summary>
|
|
||||
/// <param name="elementId">the id of the element to get its rectangle</param>
|
|
||||
/// <returns>the rectangle of the element or null if not found</returns>
|
|
||||
public virtual Rect? GetElementRectangle(string elementId) |
|
||||
{ |
|
||||
return _htmlContainer != null ? _htmlContainer.GetElementRectangle(elementId) : null; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Clear the current selection.
|
|
||||
/// </summary>
|
|
||||
public void ClearSelection() |
|
||||
{ |
|
||||
if (_htmlContainer != null) |
|
||||
_htmlContainer.ClearSelection(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
//HACK: We don't have support for RenderSize for now
|
|
||||
private Size RenderSize => new Size(Bounds.Width, Bounds.Height); |
|
||||
|
|
||||
|
|
||||
public override void Render(DrawingContext context) |
|
||||
{ |
|
||||
context.FillRectangle(Background, new Rect(RenderSize)); |
|
||||
|
|
||||
if (BorderThickness != new Thickness(0) && BorderBrush != null) |
|
||||
{ |
|
||||
var brush = new SolidColorBrush(Colors.Black); |
|
||||
if (BorderThickness.Top > 0) |
|
||||
context.FillRectangle(brush, new Rect(0, 0, RenderSize.Width, BorderThickness.Top)); |
|
||||
if (BorderThickness.Bottom > 0) |
|
||||
context.FillRectangle(brush, new Rect(0, RenderSize.Height - BorderThickness.Bottom, RenderSize.Width, BorderThickness.Bottom)); |
|
||||
if (BorderThickness.Left > 0) |
|
||||
context.FillRectangle(brush, new Rect(0, 0, BorderThickness.Left, RenderSize.Height)); |
|
||||
if (BorderThickness.Right > 0) |
|
||||
context.FillRectangle(brush, new Rect(RenderSize.Width - BorderThickness.Right, 0, BorderThickness.Right, RenderSize.Height)); |
|
||||
} |
|
||||
|
|
||||
var htmlWidth = HtmlWidth(RenderSize); |
|
||||
var htmlHeight = HtmlHeight(RenderSize); |
|
||||
if (_htmlContainer != null && htmlWidth > 0 && htmlHeight > 0) |
|
||||
{ |
|
||||
/* |
|
||||
//TODO: Revert antialiasing fixes
|
|
||||
var windows = Window.GetWindow(this); |
|
||||
if (windows != null) |
|
||||
{ |
|
||||
// adjust render location to round point so we won't get anti-alias smugness
|
|
||||
var wPoint = TranslatePoint(new Point(0, 0), windows); |
|
||||
wPoint.Offset(-(int)wPoint.X, -(int)wPoint.Y); |
|
||||
var xTrans = wPoint.X < .5 ? -wPoint.X : 1 - wPoint.X; |
|
||||
var yTrans = wPoint.Y < .5 ? -wPoint.Y : 1 - wPoint.Y; |
|
||||
context.PushTransform(new TranslateTransform(xTrans, yTrans)); |
|
||||
}*/ |
|
||||
|
|
||||
using (context.PushClip(new Rect(Padding.Left + BorderThickness.Left, Padding.Top + BorderThickness.Top, |
|
||||
htmlWidth, (int) htmlHeight))) |
|
||||
{ |
|
||||
_htmlContainer.Location = new Point(Padding.Left + BorderThickness.Left, |
|
||||
Padding.Top + BorderThickness.Top); |
|
||||
_htmlContainer.PerformPaint(context, |
|
||||
new Rect(Padding.Left + BorderThickness.Left, Padding.Top + BorderThickness.Top, htmlWidth, |
|
||||
htmlHeight)); |
|
||||
} |
|
||||
|
|
||||
if (!_lastScrollOffset.Equals(_htmlContainer.ScrollOffset)) |
|
||||
{ |
|
||||
_lastScrollOffset = _htmlContainer.ScrollOffset; |
|
||||
InvokeMouseMove(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse move to handle hover cursor and text selection.
|
|
||||
/// </summary>
|
|
||||
protected override void OnPointerMoved(PointerEventArgs e) |
|
||||
{ |
|
||||
base.OnPointerMoved(e); |
|
||||
if (_htmlContainer != null) |
|
||||
_htmlContainer.HandleMouseMove(this, e.GetPosition(this)); |
|
||||
} |
|
||||
/// <summary>
|
|
||||
/// Handle mouse leave to handle cursor change.
|
|
||||
/// </summary>
|
|
||||
protected override void OnPointerLeave(PointerEventArgs e) |
|
||||
{ |
|
||||
base.OnPointerLeave(e); |
|
||||
if (_htmlContainer != null) |
|
||||
_htmlContainer.HandleMouseLeave(this); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse down to handle selection.
|
|
||||
/// </summary>
|
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|
||||
{ |
|
||||
base.OnPointerPressed(e); |
|
||||
LeftMouseButton = true; |
|
||||
_htmlContainer?.HandleLeftMouseDown(this, e); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle mouse up to handle selection and link click.
|
|
||||
/// </summary>
|
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e) |
|
||||
{ |
|
||||
base.OnPointerReleased(e); |
|
||||
LeftMouseButton = false; |
|
||||
if (_htmlContainer != null) |
|
||||
_htmlContainer.HandleLeftMouseUp(this, e); |
|
||||
} |
|
||||
|
|
||||
//TODO: Implement double click
|
|
||||
/* |
|
||||
/// <summary>
|
|
||||
/// Handle mouse double click to select word under the mouse.
|
|
||||
/// </summary>
|
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) |
|
||||
{ |
|
||||
base.OnMouseDoubleClick(e); |
|
||||
if (_htmlContainer != null) |
|
||||
_htmlContainer.HandleMouseDoubleClick(this, e); |
|
||||
} |
|
||||
*/ |
|
||||
/// <summary>
|
|
||||
/// Handle key down event for selection, copy and scrollbars handling.
|
|
||||
/// </summary>
|
|
||||
protected override void OnKeyDown(KeyEventArgs e) |
|
||||
{ |
|
||||
base.OnKeyDown(e); |
|
||||
if (_htmlContainer != null) |
|
||||
_htmlContainer.HandleKeyDown(this, e); |
|
||||
} |
|
||||
|
|
||||
void RaiseRouted<T>(RoutedEvent ev, T arg) |
|
||||
{ |
|
||||
var e =new HtmlRendererRoutedEventArgs<T> |
|
||||
{ |
|
||||
Event = arg, |
|
||||
Source = this, |
|
||||
RoutedEvent = ev, |
|
||||
Route = ev.RoutingStrategies |
|
||||
}; |
|
||||
RaiseEvent(e); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Propagate the LoadComplete event from root container.
|
|
||||
/// </summary>
|
|
||||
protected virtual void OnLoadComplete(EventArgs e) => RaiseRouted(LoadCompleteEvent, e); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Propagate the LinkClicked event from root container.
|
|
||||
/// </summary>
|
|
||||
protected virtual void OnLinkClicked(HtmlLinkClickedEventArgs e) => RaiseRouted(LinkClickedEvent, e); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Propagate the Render Error event from root container.
|
|
||||
/// </summary>
|
|
||||
protected virtual void OnRenderError(HtmlRenderErrorEventArgs e) => RaiseRouted(RenderErrorEvent, e); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Propagate the stylesheet load event from root container.
|
|
||||
/// </summary>
|
|
||||
protected virtual void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e) => RaiseRouted(StylesheetLoadEvent, e); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Propagate the image load event from root container.
|
|
||||
/// </summary>
|
|
||||
protected virtual void OnImageLoad(HtmlImageLoadEventArgs e) => RaiseRouted(ImageLoadEvent, e); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle html renderer invalidate and re-layout as requested.
|
|
||||
/// </summary>
|
|
||||
protected virtual void OnRefresh(HtmlRefreshEventArgs e) |
|
||||
{ |
|
||||
if (e.Layout) |
|
||||
InvalidateMeasure(); |
|
||||
InvalidateVisual(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the width the HTML has to render in (not including vertical scroll iff it is visible)
|
|
||||
/// </summary>
|
|
||||
protected virtual double HtmlWidth(Size size) |
|
||||
{ |
|
||||
return size.Width - Padding.Left - Padding.Right - BorderThickness.Left - BorderThickness.Right; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get the width the HTML has to render in (not including vertical scroll iff it is visible)
|
|
||||
/// </summary>
|
|
||||
protected virtual double HtmlHeight(Size size) |
|
||||
{ |
|
||||
return size.Height - Padding.Top - Padding.Bottom - BorderThickness.Top - BorderThickness.Bottom; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// call mouse move to handle paint after scroll or html change affecting mouse cursor.
|
|
||||
/// </summary>
|
|
||||
protected virtual void InvokeMouseMove() |
|
||||
{ |
|
||||
|
|
||||
_htmlContainer.HandleMouseMove(this, (this.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(this) ?? default(Point)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle when dependency property value changes to update the underline HtmlContainer with the new value.
|
|
||||
/// </summary>
|
|
||||
private static void OnAvaloniaProperty_valueChanged(AvaloniaObject AvaloniaObject, |
|
||||
AvaloniaPropertyChangedEventArgs e) |
|
||||
{ |
|
||||
var control = AvaloniaObject as HtmlControl; |
|
||||
if (control != null) |
|
||||
{ |
|
||||
var htmlContainer = control._htmlContainer; |
|
||||
if (e.Property == AvoidImagesLateLoadingProperty) |
|
||||
{ |
|
||||
htmlContainer.AvoidImagesLateLoading = (bool) e.NewValue; |
|
||||
} |
|
||||
else if (e.Property == IsSelectionEnabledProperty) |
|
||||
{ |
|
||||
htmlContainer.IsSelectionEnabled = (bool) e.NewValue; |
|
||||
} |
|
||||
else if (e.Property == IsContextMenuEnabledProperty) |
|
||||
{ |
|
||||
htmlContainer.IsContextMenuEnabled = (bool) e.NewValue; |
|
||||
} |
|
||||
else if (e.Property == BaseStylesheetProperty) |
|
||||
{ |
|
||||
var baseCssData = CssData.Parse(AvaloniaAdapter.Instance, (string) e.NewValue); |
|
||||
control._baseCssData = baseCssData; |
|
||||
htmlContainer.SetHtml(control.Text, baseCssData); |
|
||||
} |
|
||||
else if (e.Property == TextProperty) |
|
||||
{ |
|
||||
htmlContainer.ScrollOffset = new Point(0, 0); |
|
||||
htmlContainer.SetHtml((string) e.NewValue, control._baseCssData); |
|
||||
control.InvalidateMeasure(); |
|
||||
control.InvalidateVisual(); |
|
||||
|
|
||||
if (control.VisualRoot != null) |
|
||||
{ |
|
||||
control.InvokeMouseMove(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
//TODO: Implement CheckAccess calls
|
|
||||
/* |
|
||||
private void OnLoadComplete(object sender, EventArgs e) |
|
||||
{ |
|
||||
|
|
||||
if (CheckAccess()) |
|
||||
OnLoadComplete(e); |
|
||||
else |
|
||||
Dispatcher.UIThread.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
private void OnLinkClicked(object sender, HtmlLinkClickedEventArgs e) |
|
||||
{ |
|
||||
if (CheckAccess()) |
|
||||
OnLinkClicked(e); |
|
||||
else |
|
||||
Dispatcher.UIThread.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e); |
|
||||
} |
|
||||
|
|
||||
private void OnRenderError(object sender, HtmlRenderErrorEventArgs e) |
|
||||
{ |
|
||||
if (CheckAccess()) |
|
||||
OnRenderError(e); |
|
||||
else |
|
||||
Dispatcher.UIThread.Invoke(new Action<HtmlRenderErrorEventArgs>(OnRenderError), e); |
|
||||
} |
|
||||
|
|
||||
private void OnStylesheetLoad(object sender, HtmlStylesheetLoadEventArgs e) |
|
||||
{ |
|
||||
if (CheckAccess()) |
|
||||
OnStylesheetLoad(e); |
|
||||
else |
|
||||
Dispatcher.UIThread.Invoke(new Action<HtmlStylesheetLoadEventArgs>(OnStylesheetLoad), e); |
|
||||
} |
|
||||
|
|
||||
private void OnImageLoad(object sender, HtmlImageLoadEventArgs e) |
|
||||
{ |
|
||||
if (CheckAccess()) |
|
||||
OnImageLoad(e); |
|
||||
else |
|
||||
Dispatcher.UIThread.Invoke(new Action<HtmlImageLoadEventArgs>(OnImageLoad), e); |
|
||||
} |
|
||||
|
|
||||
private void OnRefresh(object sender, HtmlRefreshEventArgs e) |
|
||||
{ |
|
||||
if (CheckAccess()) |
|
||||
OnRefresh(e); |
|
||||
else |
|
||||
Dispatcher.UIThread.Invoke(new Action<HtmlRefreshEventArgs>(OnRefresh), e); |
|
||||
} |
|
||||
*/ |
|
||||
} |
|
||||
} |
|
||||
@ -1,112 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using System; |
|
||||
using Avalonia.HtmlRenderer; |
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
using TheArtOfDev.HtmlRenderer.Core; |
|
||||
using TheArtOfDev.HtmlRenderer.Avalonia.Adapters; |
|
||||
|
|
||||
namespace Avalonia.Controls.Html |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides HTML rendering using the text property.<br/>
|
|
||||
/// WPF control that will render html content in it's client rectangle.<br/>
|
|
||||
/// Using <see cref="AutoSize"/> and <see cref="AutoSizeHeightOnly"/> client can control how the html content effects the
|
|
||||
/// size of the label. Either case scrollbars are never shown and html content outside of client bounds will be clipped.
|
|
||||
/// MaxWidth/MaxHeight and MinWidth/MinHeight with AutoSize can limit the max/min size of the control<br/>
|
|
||||
/// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.<br/>
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// See <see cref="HtmlControl"/> for more info.
|
|
||||
/// </remarks>
|
|
||||
public class HtmlLabel : HtmlControl |
|
||||
{ |
|
||||
#region Dependency properties
|
|
||||
|
|
||||
public static readonly AvaloniaProperty AutoSizeProperty = PropertyHelper.Register<HtmlLabel, bool>("AutoSize", true, OnAvaloniaProperty_valueChanged); |
|
||||
public static readonly AvaloniaProperty AutoSizeHeightOnlyProperty = PropertyHelper.Register<HtmlLabel, bool>("AutoSizeHeightOnly", false, OnAvaloniaProperty_valueChanged); |
|
||||
|
|
||||
#endregion
|
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Init.
|
|
||||
/// </summary>
|
|
||||
static HtmlLabel() |
|
||||
{ |
|
||||
BackgroundProperty.OverrideDefaultValue<HtmlLabel>(Brushes.Transparent); |
|
||||
} |
|
||||
|
|
||||
#region Private methods
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Perform the layout of the html in the control.
|
|
||||
/// </summary>
|
|
||||
protected override Size MeasureOverride(Size constraint) |
|
||||
{ |
|
||||
if (_htmlContainer != null) |
|
||||
{ |
|
||||
using (var ig = new GraphicsAdapter()) |
|
||||
{ |
|
||||
var horizontal = Padding.Left + Padding.Right + BorderThickness.Left + BorderThickness.Right; |
|
||||
var vertical = Padding.Top + Padding.Bottom + BorderThickness.Top + BorderThickness.Bottom; |
|
||||
|
|
||||
var size = new Size(Math.Min(MaxWidth, constraint.Width), Math.Min(MaxHeight, constraint.Height)); |
|
||||
var maxSize = new RSize(size.Width < Double.PositiveInfinity ? size.Width - horizontal : 0, size.Height < Double.PositiveInfinity ? size.Height - vertical : 0); |
|
||||
_htmlContainer.HtmlContainerInt.MaxSize = maxSize; |
|
||||
|
|
||||
_htmlContainer.HtmlContainerInt.PerformLayout(ig); |
|
||||
var newSize = _htmlContainer.ActualSize; |
|
||||
constraint = new Size(newSize.Width + horizontal, newSize.Height + vertical); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (double.IsPositiveInfinity(constraint.Width) || double.IsPositiveInfinity(constraint.Height)) |
|
||||
constraint = Size.Empty; |
|
||||
|
|
||||
return constraint; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Handle when dependency property value changes to update the underline HtmlContainer with the new value.
|
|
||||
/// </summary>
|
|
||||
private static void OnAvaloniaProperty_valueChanged(AvaloniaObject AvaloniaObject, AvaloniaPropertyChangedEventArgs e) |
|
||||
{ |
|
||||
var control = AvaloniaObject as HtmlLabel; |
|
||||
if (control != null) |
|
||||
{ |
|
||||
if (e.Property == AutoSizeProperty) |
|
||||
{ |
|
||||
if ((bool)e.NewValue) |
|
||||
{ |
|
||||
AvaloniaObject.SetValue(AutoSizeHeightOnlyProperty, false); |
|
||||
control.InvalidateMeasure(); |
|
||||
control.InvalidateVisual(); |
|
||||
} |
|
||||
} |
|
||||
else if (e.Property == AutoSizeHeightOnlyProperty) |
|
||||
{ |
|
||||
if ((bool)e.NewValue) |
|
||||
{ |
|
||||
AvaloniaObject.SetValue(AutoSizeProperty, false); |
|
||||
control.InvalidateMeasure(); |
|
||||
control.InvalidateVisual(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
#endregion
|
|
||||
} |
|
||||
} |
|
||||
@ -1,14 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
using Avalonia.Interactivity; |
|
||||
|
|
||||
namespace Avalonia.Controls.Html |
|
||||
{ |
|
||||
public class HtmlRendererRoutedEventArgs<T> : RoutedEventArgs |
|
||||
{ |
|
||||
public T Event { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,33 +0,0 @@ |
|||||
using System.Resources; |
|
||||
using System.Reflection; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using Avalonia.Metadata; |
|
||||
|
|
||||
// General Information about an assembly is controlled through the following
|
|
||||
// set of attributes. Change these attribute values to modify the information
|
|
||||
// associated with an assembly.
|
|
||||
[assembly: AssemblyTitle("Avalonia.HtmlRenderer")] |
|
||||
[assembly: AssemblyDescription("")] |
|
||||
[assembly: AssemblyConfiguration("")] |
|
||||
[assembly: AssemblyCompany("")] |
|
||||
[assembly: AssemblyProduct("Avalonia.HtmlRenderer")] |
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")] |
|
||||
[assembly: AssemblyTrademark("")] |
|
||||
[assembly: AssemblyCulture("")] |
|
||||
[assembly: NeutralResourcesLanguage("en")] |
|
||||
|
|
||||
// Version information for an assembly consists of the following four values:
|
|
||||
//
|
|
||||
// Major Version
|
|
||||
// Minor Version
|
|
||||
// Build Number
|
|
||||
// Revision
|
|
||||
//
|
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||
// by using the '*' as shown below:
|
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||
[assembly: AssemblyVersion("1.0.0.0")] |
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")] |
|
||||
|
|
||||
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Html")] |
|
||||
@ -1,24 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Avalonia.HtmlRenderer |
|
||||
{ |
|
||||
static class PropertyHelper |
|
||||
{ |
|
||||
|
|
||||
public static AvaloniaProperty Register<TOwner, T>(string name, T def, Action<AvaloniaObject, AvaloniaPropertyChangedEventArgs> changed) where TOwner : AvaloniaObject |
|
||||
{ |
|
||||
var pp = AvaloniaProperty.Register<TOwner, T>(name, def); |
|
||||
Action<AvaloniaPropertyChangedEventArgs> cb = args => |
|
||||
{ |
|
||||
changed(args.Sender, args); |
|
||||
}; |
|
||||
|
|
||||
pp.Changed.Subscribe(cb); |
|
||||
return pp; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,123 +0,0 @@ |
|||||
// "Therefore those skilled at the unorthodox
|
|
||||
// are infinite as heaven and earth,
|
|
||||
// inexhaustible as the great rivers.
|
|
||||
// When they come to an end,
|
|
||||
// they begin again,
|
|
||||
// like the days and months;
|
|
||||
// they die and are reborn,
|
|
||||
// like the four seasons."
|
|
||||
//
|
|
||||
// - Sun Tsu,
|
|
||||
// "The Art of War"
|
|
||||
|
|
||||
using Avalonia; |
|
||||
using Avalonia.Media; |
|
||||
using TheArtOfDev.HtmlRenderer.Adapters.Entities; |
|
||||
|
|
||||
namespace TheArtOfDev.HtmlRenderer.Avalonia.Utilities |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Utilities for converting WPF entities to HtmlRenderer core entities.
|
|
||||
/// </summary>
|
|
||||
internal static class Util |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Convert from WPF point to core point.
|
|
||||
/// </summary>
|
|
||||
public static RPoint Convert(Point p) |
|
||||
{ |
|
||||
return new RPoint(p.X, p.Y); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from WPF point to core point.
|
|
||||
/// </summary>
|
|
||||
public static Point[] Convert(RPoint[] points) |
|
||||
{ |
|
||||
Point[] myPoints = new Point[points.Length]; |
|
||||
for (int i = 0; i < points.Length; i++) |
|
||||
myPoints[i] = Convert(points[i]); |
|
||||
return myPoints; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from core point to WPF point.
|
|
||||
/// </summary>
|
|
||||
public static Point Convert(RPoint p) |
|
||||
{ |
|
||||
return new Point(p.X, p.Y); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from core point to WPF point.
|
|
||||
/// </summary>
|
|
||||
public static Point ConvertRound(RPoint p) |
|
||||
{ |
|
||||
return new Point((int)p.X, (int)p.Y); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from WPF size to core size.
|
|
||||
/// </summary>
|
|
||||
public static RSize Convert(Size s) |
|
||||
{ |
|
||||
return new RSize(s.Width, s.Height); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from core size to WPF size.
|
|
||||
/// </summary>
|
|
||||
public static Size Convert(RSize s) |
|
||||
{ |
|
||||
return new Size(s.Width, s.Height); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from core point to WPF point.
|
|
||||
/// </summary>
|
|
||||
public static Size ConvertRound(RSize s) |
|
||||
{ |
|
||||
return new Size((int)s.Width, (int)s.Height); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from WPF rectangle to core rectangle.
|
|
||||
/// </summary>
|
|
||||
public static RRect Convert(Rect r) |
|
||||
{ |
|
||||
return new RRect(r.X, r.Y, r.Width, r.Height); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from core rectangle to WPF rectangle.
|
|
||||
/// </summary>
|
|
||||
public static Rect Convert(RRect r) |
|
||||
{ |
|
||||
return new Rect(r.X, r.Y, r.Width, r.Height); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from core rectangle to WPF rectangle.
|
|
||||
/// </summary>
|
|
||||
public static Rect ConvertRound(RRect r) |
|
||||
{ |
|
||||
return new Rect((int)r.X, (int)r.Y, (int)r.Width, (int)r.Height); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from WPF color to core color.
|
|
||||
/// </summary>
|
|
||||
public static RColor Convert(Color c) |
|
||||
{ |
|
||||
return RColor.FromArgb(c.A, c.R, c.G, c.B); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Convert from core color to WPF color.
|
|
||||
/// </summary>
|
|
||||
public static Color Convert(RColor c) |
|
||||
{ |
|
||||
return Color.FromArgb(c.A, c.R, c.G, c.B); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1 +0,0 @@ |
|||||
Subproject commit 43df78f4d1bb09d05021f062cb15e939e3e1771d |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue