├── test └── AvaloniaHex.Tests │ ├── GlobalUsings.cs │ ├── Document │ ├── BitLocationTest.cs │ └── BitRangeTest.cs │ └── AvaloniaHex.Tests.csproj ├── assets └── demo.gif ├── examples └── AvaloniaHex.Demo │ ├── Resources │ └── SourceCodePro-Regular.ttf │ ├── App.axaml.cs │ ├── App.axaml │ ├── Program.cs │ ├── InputDialog.axaml │ ├── InputDialog.axaml.cs │ ├── RealTimeChangingDocument.cs │ ├── AvaloniaHex.Demo.csproj │ ├── SegmentedDocument.cs │ ├── MainWindow.axaml │ └── .gitignore ├── src └── AvaloniaHex │ ├── Rendering │ ├── ILineTransformer.cs │ ├── InvalidRangesHighlighter.cs │ ├── ZeroesHighlighter.cs │ ├── RangesHighlighter.cs │ ├── SimpleTextSource.cs │ ├── ColumnBackgroundLayer.cs │ ├── Layer.cs │ ├── LayerRenderMoments.cs │ ├── TextLayer.cs │ ├── TextRunExtensions.cs │ ├── VisualBytesLineSegment.cs │ ├── CellGeometryBuilder.cs │ ├── OffsetColumn.cs │ ├── HeaderLayer.cs │ ├── ByteHighlighter.cs │ ├── CellGroupsLayer.cs │ ├── VisualBytesLinesBuffer.cs │ ├── AsciiColumn.cs │ ├── VisualBytesLine.cs │ ├── BinaryColumn.cs │ ├── HexColumn.cs │ └── Column.cs │ ├── Editing │ ├── EditingMode.cs │ ├── Selection.cs │ ├── CurrentLineLayer.cs │ ├── SelectionLayer.cs │ ├── CaretLayer.cs │ └── Caret.cs │ ├── Document │ ├── DocumentChangedEventArgs.cs │ ├── BinaryDocumentChange.cs │ ├── ReadOnlyBitRangeUnion.cs │ ├── IReadOnlyBitRangeUnion.cs │ ├── IBinaryDocument.cs │ ├── ByteArrayBinaryDocument.cs │ ├── MemoryBinaryDocument.cs │ ├── DynamicBinaryDocument.cs │ ├── MemoryMappedBinaryDocument.cs │ ├── BitRange.cs │ ├── BitRangeUnion.cs │ └── BitLocation.cs │ ├── HexEditor.axaml │ ├── AvaloniaHex.csproj │ └── Themes │ ├── Base.axaml │ ├── Simple │ └── AvaloniaHex.axaml │ └── Fluent │ └── AvaloniaHex.axaml ├── appveyor.yml ├── Directory.Build.props ├── LICENSE.md ├── README.md ├── AvaloniaHex.sln └── .gitignore /test/AvaloniaHex.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Washi1337/AvaloniaHex/HEAD/assets/demo.gif -------------------------------------------------------------------------------- /examples/AvaloniaHex.Demo/Resources/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Washi1337/AvaloniaHex/HEAD/examples/AvaloniaHex.Demo/Resources/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/ILineTransformer.cs: -------------------------------------------------------------------------------- 1 | namespace AvaloniaHex.Rendering; 2 | 3 | /// 4 | /// Provides members for transforming a visual line. 5 | /// 6 | public interface ILineTransformer 7 | { 8 | /// 9 | /// Transforms a visual line. 10 | /// 11 | /// 12 | /// The line to transform. 13 | void Transform(HexView hexView, VisualBytesLine line); 14 | } 15 | -------------------------------------------------------------------------------- /src/AvaloniaHex/Editing/EditingMode.cs: -------------------------------------------------------------------------------- 1 | namespace AvaloniaHex.Editing; 2 | 3 | /// 4 | /// Provides members describing all possible editing modes a caret in a hex editor can be. 5 | /// 6 | public enum EditingMode 7 | { 8 | /// 9 | /// Indicates the cursor is overwriting the existing bytes. 10 | /// 11 | Overwrite, 12 | 13 | /// 14 | /// Indicates the cursor is inserting new bytes. 15 | /// 16 | Insert 17 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/InvalidRangesHighlighter.cs: -------------------------------------------------------------------------------- 1 | using AvaloniaHex.Document; 2 | 3 | namespace AvaloniaHex.Rendering; 4 | 5 | /// 6 | /// Provides an implementation of a highlighter that highlights all invalid ranges in a document. 7 | /// 8 | public class InvalidRangesHighlighter : ByteHighlighter 9 | { 10 | /// 11 | protected override bool IsHighlighted(HexView hexView, VisualBytesLine line, BitLocation location) 12 | { 13 | return !hexView.Document?.ValidRanges.Contains(location) ?? false; 14 | } 15 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | - 2 | branches: 3 | only: 4 | - main 5 | 6 | image: Visual Studio 2022 7 | version: 0.1.10-master-build.{build} 8 | configuration: Release 9 | 10 | before_build: 11 | - dotnet restore 12 | 13 | build: 14 | verbosity: minimal 15 | 16 | artifacts: 17 | - path: 'src\AvaloniaHex\bin\Release\*.nupkg' 18 | 19 | deploy: 20 | provider: NuGet 21 | api_key: 22 | secure: HyapzsqHiM9VMD2qxG9cPHTu+j4o8A5/sEKY3duRML7uw1JtxcWQFHy1GLy3HMjr 23 | skip_symbols: false 24 | artifact: /.*\.nupkg/ 25 | -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/ZeroesHighlighter.cs: -------------------------------------------------------------------------------- 1 | using AvaloniaHex.Document; 2 | 3 | namespace AvaloniaHex.Rendering; 4 | 5 | /// 6 | /// Provides an implementation of a highlighter that highlights all zero bytes in a visual line. 7 | /// 8 | public class ZeroesHighlighter : ByteHighlighter 9 | { 10 | /// 11 | protected override bool IsHighlighted(HexView hexView, VisualBytesLine line, BitLocation location) 12 | { 13 | return hexView.Document!.ValidRanges.Contains(location) && line.GetByteAtAbsolute(location.ByteIndex) == 0; 14 | } 15 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © Washi 2024-2025 5 | https://github.com/Washi1337/AvaloniaHex 6 | https://github.com/Washi1337/AvaloniaHex/LICENSE.md 7 | https://github.com/Washi1337/AvaloniaHex 8 | git 9 | 12 10 | 0.1.10 11 | true 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/RangesHighlighter.cs: -------------------------------------------------------------------------------- 1 | using AvaloniaHex.Document; 2 | 3 | namespace AvaloniaHex.Rendering; 4 | 5 | /// 6 | /// Highlights ranges of bytes within a document of a hex view. 7 | /// 8 | public class RangesHighlighter : ByteHighlighter 9 | { 10 | /// 11 | /// Gets the bit ranges that should be highlighted in the document. 12 | /// 13 | public BitRangeUnion Ranges { get; } = new(); 14 | 15 | /// 16 | protected override bool IsHighlighted(HexView hexView, VisualBytesLine line, BitLocation location) 17 | { 18 | return Ranges.Contains(location); 19 | } 20 | } -------------------------------------------------------------------------------- /examples/AvaloniaHex.Demo/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | 5 | namespace AvaloniaHex.Demo 6 | { 7 | public partial class App : Application 8 | { 9 | public override void Initialize() 10 | { 11 | AvaloniaXamlLoader.Load(this); 12 | } 13 | 14 | public override void OnFrameworkInitializationCompleted() 15 | { 16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 17 | { 18 | desktop.MainWindow = new MainWindow(); 19 | } 20 | 21 | base.OnFrameworkInitializationCompleted(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /examples/AvaloniaHex.Demo/App.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | avares://AvaloniaHex.Demo/Resources#Source Code Pro 14 | 15 | -------------------------------------------------------------------------------- /examples/AvaloniaHex.Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | 4 | namespace AvaloniaHex.Demo 5 | { 6 | class Program 7 | { 8 | // Initialization code. Don't use any Avalonia, third-party APIs or any 9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 10 | // yet and stuff might break. 11 | [STAThread] 12 | public static void Main(string[] args) => BuildAvaloniaApp() 13 | .StartWithClassicDesktopLifetime(args); 14 | 15 | // Avalonia configuration, don't remove; also used by visual designer. 16 | public static AppBuilder BuildAvaloniaApp() 17 | => AppBuilder.Configure() 18 | .UsePlatformDetect() 19 | .LogToTrace(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/SimpleTextSource.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media.TextFormatting; 2 | 3 | namespace AvaloniaHex.Rendering; 4 | 5 | /// 6 | /// Wraps a string into a instance. 7 | /// 8 | internal readonly struct SimpleTextSource : ITextSource 9 | { 10 | private readonly TextRunProperties _defaultProperties; 11 | private readonly string _text; 12 | 13 | public SimpleTextSource(string text, TextRunProperties defaultProperties) 14 | { 15 | _text = text; 16 | _defaultProperties = defaultProperties; 17 | } 18 | 19 | public TextRun GetTextRun(int textSourceIndex) 20 | { 21 | if (textSourceIndex >= _text.Length) 22 | return new TextEndOfParagraph(); 23 | 24 | return new TextCharacters(_text, _defaultProperties); 25 | } 26 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/ColumnBackgroundLayer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Media; 2 | 3 | namespace AvaloniaHex.Rendering; 4 | 5 | /// 6 | /// Represents the column background rendering layer in a hex view. 7 | /// 8 | public class ColumnBackgroundLayer : Layer 9 | { 10 | /// 11 | public override LayerRenderMoments UpdateMoments => LayerRenderMoments.Minimal; 12 | 13 | /// 14 | public override void Render(DrawingContext context) 15 | { 16 | base.Render(context); 17 | 18 | if (HexView is null) 19 | return; 20 | 21 | foreach (var column in HexView.Columns) 22 | { 23 | if (column.Background is not null || column.Border is not null) 24 | context.DrawRectangle(column.Background, column.Border, column.Bounds); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Document/DocumentChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace AvaloniaHex.Document; 2 | 3 | /// 4 | /// Describes a change of documents in a hex view or editor. 5 | /// 6 | public class DocumentChangedEventArgs : EventArgs 7 | { 8 | /// 9 | /// Constructs a new document change event. 10 | /// 11 | /// The old document. 12 | /// The new document. 13 | public DocumentChangedEventArgs(IBinaryDocument? old, IBinaryDocument? @new) 14 | { 15 | Old = old; 16 | New = @new; 17 | } 18 | 19 | /// 20 | /// Gets the original document. 21 | /// 22 | public IBinaryDocument? Old { get; } 23 | 24 | /// 25 | /// Gets the new document. 26 | /// 27 | public IBinaryDocument? New { get; } 28 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/Layer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Media; 3 | using AvaloniaHex.Editing; 4 | 5 | namespace AvaloniaHex.Rendering; 6 | 7 | /// 8 | /// Represents a single layer in the hex view rendering. 9 | /// 10 | public abstract class Layer : Control 11 | { 12 | /// 13 | /// Gets a value indicating when the layer should be rendered. 14 | /// 15 | public virtual LayerRenderMoments UpdateMoments => LayerRenderMoments.Always; 16 | 17 | /// 18 | /// Gets the parent hex view the layer is added to. 19 | /// 20 | public HexView? HexView 21 | { 22 | get; 23 | internal set; 24 | } 25 | 26 | /// 27 | public override void Render(DrawingContext context) 28 | { 29 | base.Render(context); 30 | } 31 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/LayerRenderMoments.cs: -------------------------------------------------------------------------------- 1 | namespace AvaloniaHex.Rendering; 2 | 3 | /// 4 | /// Provides members describing when a layer should be rendered. 5 | /// 6 | [Flags] 7 | public enum LayerRenderMoments 8 | { 9 | /// 10 | /// Indicates the layer should only be rendered minimally. 11 | /// 12 | Minimal = 0, 13 | 14 | /// 15 | /// Indicates the layer should be rendered when a rearrange of the text is queued. 16 | /// 17 | NoResizeRearrange = 1, 18 | 19 | /// 20 | /// Indicates the layer should be rendered when a single line was invalidated. 21 | /// 22 | LineInvalidate = 2, 23 | 24 | /// 25 | /// Indicates the layer should always be rendered on every update. 26 | /// 27 | Always = NoResizeRearrange | LineInvalidate, 28 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Document/BinaryDocumentChange.cs: -------------------------------------------------------------------------------- 1 | namespace AvaloniaHex.Document; 2 | 3 | /// 4 | /// Describes a change in a binary document. 5 | /// 6 | /// The action that was performed. 7 | /// The range within the document that was affected. 8 | public record struct BinaryDocumentChange(BinaryDocumentChangeType Type, BitRange AffectedRange); 9 | 10 | /// 11 | /// Provides members describing the possible actions that can be applied to a document. 12 | /// 13 | public enum BinaryDocumentChangeType 14 | { 15 | /// 16 | /// Indicates the document was modified in-place. 17 | /// 18 | Modify, 19 | 20 | /// 21 | /// Indicates bytes were inserted into the document. 22 | /// 23 | Insert, 24 | 25 | /// 26 | /// Indicates the bytes were removed from the document. 27 | /// 28 | Remove, 29 | } -------------------------------------------------------------------------------- /src/AvaloniaHex/Rendering/TextLayer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Media; 3 | 4 | namespace AvaloniaHex.Rendering; 5 | 6 | /// 7 | /// Represents the layer that renders the text in a hex view. 8 | /// 9 | public class TextLayer : Layer 10 | { 11 | /// 12 | public override void Render(DrawingContext context) 13 | { 14 | base.Render(context); 15 | 16 | if (HexView is null) 17 | return; 18 | 19 | double currentY = HexView.EffectiveHeaderSize; 20 | for (int i = 0; i < HexView.VisualLines.Count; i++) 21 | { 22 | var line = HexView.VisualLines[i]; 23 | foreach (var column in HexView.Columns) 24 | { 25 | if (column.IsVisible) 26 | line.ColumnTextLines[column.Index]?.Draw(context, new Point(column.Bounds.Left, currentY)); 27 | } 28 | 29 | currentY += line.Bounds.Height; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /test/AvaloniaHex.Tests/Document/BitLocationTest.cs: -------------------------------------------------------------------------------- 1 | using AvaloniaHex.Document; 2 | 3 | namespace AvaloniaHex.Tests.Document; 4 | 5 | public class BitLocationTest 6 | { 7 | [Fact] 8 | public void InvalidBitIndex() 9 | { 10 | Assert.Throws(() => new BitLocation(0, 8)); 11 | Assert.Throws(() => new BitLocation(0, -3)); 12 | } 13 | 14 | [Theory] 15 | [InlineData(0, 0, 3, 0, 3)] 16 | [InlineData(0, 0, 8+8+8+3, 3, 3)] 17 | [InlineData(0, 2, 3, 0, 5)] 18 | [InlineData(0, 7, 1, 1, 0)] 19 | [InlineData(0, 7, 3, 1, 2)] 20 | [InlineData(0, 7, 8+8+8+3, 4, 2)] 21 | public void AddBits(ulong startByteIndex, int startBitIndex, ulong bitCount, ulong endByteIndex, int endBitIndex) 22 | { 23 | var location = new BitLocation(startByteIndex, startBitIndex); 24 | var newLocation = location.AddBits(bitCount); 25 | Assert.Equal(new BitLocation(endByteIndex, endBitIndex), newLocation); 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2024-2025` `Washi` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /test/AvaloniaHex.Tests/AvaloniaHex.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/AvaloniaHex/HexEditor.axaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 14 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/AvaloniaHex/AvaloniaHex.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AvaloniaHex 5 | A hex editor control for the Avalonia UI framework. 6 | avalonia ui hex editor binary data visualizer 7 | net6.0;net8.0 8 | enable 9 | enable 10 | true 11 | true 12 | README.md 13 | MIT 14 | 15 | 16 | 17 | bin\Debug\net6.0\AvaloniaHex.xml 18 | 19 | 20 | 21 | bin\Release\net6.0\AvaloniaHex.xml 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/AvaloniaHex.Demo/InputDialog.axaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 19 | 20 |