仿真平台内核初版 -tlib库 包含<sparc arm riscv powerPC>
This commit is contained in:
389
lib/termsharp/Rows/MonospaceTextRow.cs
Normal file
389
lib/termsharp/Rows/MonospaceTextRow.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
//
|
||||
// Copyright (c) Antmicro
|
||||
//
|
||||
// Full license details are defined in the 'LICENSE' file.
|
||||
//
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TermSharp.Misc;
|
||||
using Xwt;
|
||||
using Xwt.Drawing;
|
||||
|
||||
namespace TermSharp.Rows
|
||||
{
|
||||
public class MonospaceTextRow : IRow
|
||||
{
|
||||
public MonospaceTextRow(string content)
|
||||
{
|
||||
Debug.Assert(!content.Contains("\n"));
|
||||
this.content = content;
|
||||
lengthInTextElements = new StringInfo(content).LengthInTextElements;
|
||||
}
|
||||
|
||||
public virtual double PrepareForDrawing(ILayoutParameters parameters)
|
||||
{
|
||||
cursorInRow = null;
|
||||
defaultForeground = parameters.DefaultForeground;
|
||||
defaultBackground = parameters.DefaultBackground;
|
||||
selectionColor = parameters.SelectionColor;
|
||||
textLayout = RowUtils.TextLayoutCache.GetValue(parameters);
|
||||
lineSize = RowUtils.LineSizeCache.GetValue(parameters);
|
||||
charWidth = RowUtils.CharSizeCache.GetValue(parameters).Width;
|
||||
|
||||
if(lineSize.Width == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Math.Max used to prevent division by zero errors on windows narrower than one character
|
||||
MaximalColumn = Math.Max(((int)(lineSize.Width / charWidth)) - 1, 0);
|
||||
|
||||
var charsOnLine = MaximalColumn + 1;
|
||||
var lengthInTextElementsAtLeastOne = lengthInTextElements == 0 ? 1 : lengthInTextElements; // because even empty line has height of one line
|
||||
SublineCount = Math.Max(minimalSublineCount, DivisionWithCeiling(lengthInTextElementsAtLeastOne, charsOnLine));
|
||||
return lineSize.Height * SublineCount;
|
||||
}
|
||||
|
||||
public virtual void Draw(Context ctx, Rectangle selectedArea, SelectionDirection selectionDirection, SelectionMode selectionMode)
|
||||
{
|
||||
ctx.SetColor(defaultForeground);
|
||||
var newLinesAt = new List<int> { 0 }; // contains indices of line wraps (i.e. \n)
|
||||
var charsOnLine = MaximalColumn + 1;
|
||||
|
||||
var result = new StringBuilder();
|
||||
var enumerator = StringInfo.GetTextElementEnumerator(content);
|
||||
var textElementsThisLine = 0;
|
||||
while(enumerator.MoveNext())
|
||||
{
|
||||
textElementsThisLine++;
|
||||
result.Append(enumerator.GetTextElement());
|
||||
if(textElementsThisLine == charsOnLine)
|
||||
{
|
||||
result.Append('\n');
|
||||
newLinesAt.Add(enumerator.ElementIndex + newLinesAt.Count + 1);
|
||||
textElementsThisLine = 0;
|
||||
}
|
||||
}
|
||||
textLayout.Text = result.ToString();
|
||||
|
||||
var foregroundColors = specialForegrounds != null ? specialForegrounds.ToDictionary(x => x.Key + x.Key / charsOnLine, x => x.Value) : new Dictionary<int, Color>();
|
||||
var backgroundColors = specialBackgrounds != null ? specialBackgrounds.ToDictionary(x => x.Key + x.Key / charsOnLine, x => x.Value) : new Dictionary<int, Color>();
|
||||
if(selectedArea != default(Rectangle) && lengthInTextElements > 0)
|
||||
{
|
||||
var textWithNewLines = textLayout.Text;
|
||||
|
||||
var firstSubrow = (int)Math.Min(newLinesAt.Count - 1, Math.Floor(selectedArea.Y / lineSize.Height));
|
||||
var lastSubrow = (int)Math.Min(newLinesAt.Count - 1, Math.Floor((selectedArea.Y + selectedArea.Height) / lineSize.Height));
|
||||
var firstColumn = (int)Math.Round(selectedArea.X / charWidth);
|
||||
var lastColumn = (int)Math.Floor((selectedArea.X + selectedArea.Width) / charWidth);
|
||||
if(selectionMode == SelectionMode.Block)
|
||||
{
|
||||
for(var i = firstSubrow; i <= lastSubrow; i++)
|
||||
{
|
||||
for(var j = firstColumn; j <= lastColumn; j++)
|
||||
{
|
||||
foregroundColors[(charsOnLine + 1) * i + j] = GetSelectionForegroundColor(i, charsOnLine);
|
||||
backgroundColors[(charsOnLine + 1) * i + j] = selectionColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var hasWrapping = firstSubrow != lastSubrow;
|
||||
var endsInThisRow = selectedArea.BottomRight.Y < LineHeight * SublineCount;
|
||||
var startsBeforeThisRow = selectedArea.TopLeft.X == 0 && selectedArea.TopLeft.Y == 0;
|
||||
if(hasWrapping && endsInThisRow && !startsBeforeThisRow)
|
||||
{
|
||||
// In those cases we want to start at the top right of the selected area rect, not the top left
|
||||
if(selectionDirection == SelectionDirection.SW || selectionDirection == SelectionDirection.NE)
|
||||
{
|
||||
Utilities.Swap(ref firstColumn, ref lastColumn);
|
||||
}
|
||||
}
|
||||
|
||||
if(selectionDirection == SelectionDirection.NW)
|
||||
{
|
||||
Utilities.Swap(ref firstColumn, ref lastColumn);
|
||||
Utilities.Swap(ref firstSubrow, ref lastSubrow);
|
||||
}
|
||||
|
||||
var firstIndex = firstColumn + newLinesAt[firstSubrow];
|
||||
var lastIndex = lastColumn + newLinesAt[lastSubrow];
|
||||
|
||||
if(lastIndex < firstIndex)
|
||||
{
|
||||
Utilities.Swap(ref firstIndex, ref lastIndex);
|
||||
}
|
||||
|
||||
var textWithNewLinesStringInfo = new StringInfo(textWithNewLines);
|
||||
if(firstIndex > textWithNewLinesStringInfo.LengthInTextElements - 1)
|
||||
{
|
||||
selectedContent = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstIndex = Math.Max(0, Math.Min(textWithNewLinesStringInfo.LengthInTextElements - 1, firstIndex));
|
||||
lastIndex = Math.Max(0, Math.Min(textWithNewLinesStringInfo.LengthInTextElements - 1, lastIndex));
|
||||
|
||||
for(var i = firstIndex; i <= lastIndex; i++)
|
||||
{
|
||||
foregroundColors[i] = GetSelectionForegroundColor(i, charsOnLine);
|
||||
backgroundColors[i] = selectionColor;
|
||||
}
|
||||
selectedContent = textWithNewLinesStringInfo.SubstringByTextElements(firstIndex, lastIndex - firstIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedContent = null;
|
||||
}
|
||||
|
||||
textLayout.SetForeground(defaultForeground, 0, textLayout.Text.Length);
|
||||
foreach(var entry in GetColorRanges(foregroundColors))
|
||||
{
|
||||
textLayout.SetForeground(entry.Item3, entry.Item1, entry.Item2);
|
||||
}
|
||||
textLayout.SetBackground(defaultBackground, 0, textLayout.Text.Length);
|
||||
foreach(var entry in GetColorRanges(backgroundColors))
|
||||
{
|
||||
textLayout.SetBackground(entry.Item3, entry.Item1, entry.Item2);
|
||||
}
|
||||
if(cursorInRow.HasValue)
|
||||
{
|
||||
// we draw a rectangle AND set background so that one can see cursor in a row without character
|
||||
textLayout.SetForeground(defaultBackground, cursorInRow.Value, 1);
|
||||
}
|
||||
ctx.DrawTextLayout(textLayout, 0, 0);
|
||||
textLayout.ClearAttributes();
|
||||
}
|
||||
|
||||
public void ResetSelection()
|
||||
{
|
||||
selectedContent = null;
|
||||
}
|
||||
|
||||
public virtual void DrawCursor(Context ctx, int offset, bool focused)
|
||||
{
|
||||
var maxColumn = MaximalColumn + 1;
|
||||
var column = offset % maxColumn;
|
||||
var row = offset / maxColumn;
|
||||
var transparentForeground = defaultForeground; // Color is a struct
|
||||
transparentForeground.Alpha = cursorTransparency;
|
||||
ctx.SetColor(transparentForeground);
|
||||
ctx.Rectangle(new Rectangle(column * charWidth, row * lineSize.Height, charWidth, lineSize.Height));
|
||||
if(focused)
|
||||
{
|
||||
ctx.Fill();
|
||||
//We add the row number (0-based) to account for \n characters added in subrows.
|
||||
cursorInRow = offset + row;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.Stroke();
|
||||
}
|
||||
}
|
||||
|
||||
public void FillClipboardData(ClipboardData data)
|
||||
{
|
||||
if(selectedContent != null)
|
||||
{
|
||||
data.AppendText(selectedContent);
|
||||
}
|
||||
}
|
||||
|
||||
public void Erase(int from, int to, Color? background = null)
|
||||
{
|
||||
// due to TrimEnd() (near the end of this method) the number of sublines can go down - but this in fact
|
||||
// cannot happen during erase operation, so we make sure that the minimal subline count is the current count
|
||||
minimalSublineCount = SublineCount;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var stringInfo = new StringInfo(content);
|
||||
from = Math.Max(0, Math.Min(from, stringInfo.LengthInTextElements));
|
||||
|
||||
if(from > 0)
|
||||
{
|
||||
builder.Append(stringInfo.SubstringByTextElements(0, from));
|
||||
}
|
||||
if(to > from)
|
||||
{
|
||||
builder.Append(' ', to - from);
|
||||
}
|
||||
if(to < stringInfo.LengthInTextElements)
|
||||
{
|
||||
builder.Append(stringInfo.SubstringByTextElements(to));
|
||||
}
|
||||
|
||||
for(var i = from; i <= to; i++)
|
||||
{
|
||||
if(background.HasValue)
|
||||
{
|
||||
CheckDictionary(ref specialBackgrounds);
|
||||
specialBackgrounds[i] = background.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(specialBackgrounds != null)
|
||||
{
|
||||
specialBackgrounds.Remove(i);
|
||||
}
|
||||
}
|
||||
if(specialForegrounds != null)
|
||||
{
|
||||
specialForegrounds.Remove(i);
|
||||
}
|
||||
}
|
||||
content = builder.ToString().TrimEnd().PadRight(from, ' ');
|
||||
lengthInTextElements = new StringInfo(content).LengthInTextElements;
|
||||
}
|
||||
|
||||
public bool PutCharacterAt(int position, string what, Color? foreground = null, Color? background = null)
|
||||
{
|
||||
Debug.Assert(new StringInfo(what).LengthInTextElements == 1);
|
||||
|
||||
if(foreground.HasValue)
|
||||
{
|
||||
CheckDictionary(ref specialForegrounds);
|
||||
specialForegrounds[position] = foreground.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(specialForegrounds != null)
|
||||
{
|
||||
specialForegrounds.Remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
if(background.HasValue)
|
||||
{
|
||||
CheckDictionary(ref specialBackgrounds);
|
||||
specialBackgrounds[position] = background.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(specialBackgrounds != null)
|
||||
{
|
||||
specialBackgrounds.Remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
var stringInfo = new StringInfo(content);
|
||||
StringBuilder builder;
|
||||
if(position > 0)
|
||||
{
|
||||
if(lengthInTextElements <= position) // append at the end of the current string, possibly enlarging it
|
||||
{
|
||||
builder = new StringBuilder(content);
|
||||
builder.Append(' ', position - lengthInTextElements).Append(what);
|
||||
}
|
||||
else // insert in the middle of the current string
|
||||
{
|
||||
builder = new StringBuilder(stringInfo.SubstringByTextElements(0, position)).Append(what);
|
||||
}
|
||||
}
|
||||
else // insert at the beginning of the current string
|
||||
{
|
||||
builder = new StringBuilder(what);
|
||||
}
|
||||
// append the rest of the current string
|
||||
if(lengthInTextElements > position + 1)
|
||||
{
|
||||
builder.Append(stringInfo.SubstringByTextElements(position + 1));
|
||||
}
|
||||
|
||||
content = builder.ToString();
|
||||
var oldLengthInTextElements = lengthInTextElements;
|
||||
lengthInTextElements = new StringInfo(content).LengthInTextElements;
|
||||
// Math.Max used to prevent division by zero errors on windows narrower than one character
|
||||
var charsOnLine = Math.Max((int)Math.Floor(lineSize.Width / charWidth), 1);
|
||||
var result = DivisionWithCeiling(oldLengthInTextElements == 0 ? 1 : oldLengthInTextElements, charsOnLine)
|
||||
!= DivisionWithCeiling(lengthInTextElements == 0 ? 1 : lengthInTextElements, charsOnLine);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int GetColumnIndex(double x) => (int)(x / charWidth);
|
||||
public double IndexToColumn(int idx) => idx * charWidth;
|
||||
public int SublineCount { get; private set; }
|
||||
public string TextContent => content;
|
||||
|
||||
public double LineHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return lineSize.Height;
|
||||
}
|
||||
}
|
||||
|
||||
public int CurrentMaximalCursorPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return MaximalColumn * SublineCount;
|
||||
}
|
||||
}
|
||||
|
||||
public int MaximalColumn { get; private set; }
|
||||
|
||||
private void CheckDictionary(ref Dictionary<int, Color> dictionary)
|
||||
{
|
||||
if(dictionary == null)
|
||||
{
|
||||
dictionary = new Dictionary<int, Color>();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Tuple<int, int, Color>> GetColorRanges(Dictionary<int, Color> entries)
|
||||
{
|
||||
var colors = entries.Values.Distinct();
|
||||
foreach(var color in colors)
|
||||
{
|
||||
var entriesThisColor = new HashSet<int>(entries.Where(x => x.Value == color).Select(x => x.Key));
|
||||
var begins = entriesThisColor.Where(x => !entriesThisColor.Contains(x - 1)).OrderBy(x => x).ToArray();
|
||||
var ends = entriesThisColor.Where(x => !entriesThisColor.Contains(x + 1)).OrderBy(x => x).ToArray();
|
||||
for(var i = 0; i < begins.Length; i++)
|
||||
{
|
||||
yield return Tuple.Create(begins[i], ends[i] - begins[i] + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int DivisionWithCeiling(int dividend, int divisor)
|
||||
{
|
||||
Debug.Assert(divisor > 0 && dividend > 0);
|
||||
return (dividend + divisor - 1) / divisor;
|
||||
}
|
||||
|
||||
private Color GetSelectionForegroundColor(int index, int charsOnLine)
|
||||
{
|
||||
// the index may be shifted by new line characters in wrapped lines, so we subtract the amount of subrows.
|
||||
index -= index / charsOnLine;
|
||||
return (specialForegrounds != null && specialForegrounds.ContainsKey(index)) ? specialForegrounds[index].WithIncreasedLight(0.2) : defaultBackground;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[MonospaceTextRow: content={content}]";
|
||||
}
|
||||
|
||||
private double charWidth;
|
||||
private Size lineSize;
|
||||
private TextLayout textLayout;
|
||||
private string selectedContent;
|
||||
private string content;
|
||||
private const double cursorTransparency = 0.6;
|
||||
private Color defaultForeground;
|
||||
private Color defaultBackground;
|
||||
private Color selectionColor;
|
||||
private int lengthInTextElements;
|
||||
private int? cursorInRow;
|
||||
private int minimalSublineCount;
|
||||
private Dictionary<int, Color> specialForegrounds;
|
||||
private Dictionary<int, Color> specialBackgrounds;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user