/* * Copyright (C) 2013 FooProject * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Linq; using System.Collections.Generic; namespace FooEditEngine { enum AdjustFlow { Row, Col, Both, } enum TextPointSearchRange { TextAreaOnly, Full } /// /// キャレットとドキュメントの表示を担当します。レイアウト関連もこちらで行います /// sealed class EditView : ViewBase { internal const float LineMarkerThickness = 2; long tickCount; bool _CaretBlink; internal const int LineNumberLength = 6; const int UpdateAreaPaddingWidth = 2; const int UpdateAreaWidth = 4; const int UpdateAreaTotalWidth = UpdateAreaWidth + UpdateAreaPaddingWidth; /// /// コンストラクター /// public EditView(Document doc, IEditorRender r, int MarginLeftAndRight = 5) : this(doc, r, new Padding(MarginLeftAndRight, 0, MarginLeftAndRight, 0)) { } /// /// コンストラクター /// /// ドキュメント /// レンダー /// マージン(1番目:左、2番目:上、3番目:右、4番目:下) public EditView(Document doc, IEditorRender r, Padding margin) : base(doc, r, margin) { this.CaretBlinkTime = 500; this.CaretWidthOnInsertMode = 1; this.CalculateClipRect(); this.CaretLocation = new Point(this.render.TextArea.X, this.render.TextArea.Y); this.LayoutLines.FoldingCollection.StatusChanged += FoldingCollection_StatusChanged; this.IsFocused = false; } /// /// 選択範囲コレクション /// internal SelectCollection Selections { get { return this.Document.Selections; } set { this.Document.Selections = value; } } /// /// ラインマーカーを描くなら偽。そうでなければ真 /// public bool HideLineMarker { get { return this.Document.HideLineMarker; } set { this.Document.HideLineMarker = value; } } /// /// キャレットを描くなら偽。そうでなければ真 /// public bool HideCaret { get { return this.Document.HideCaret; } set { this.Document.HideCaret = value; } } /// /// 挿入モードなら真を返し、上書きモードなら偽を返す /// public bool InsertMode { get { return this.Document.InsertMode; } set { this.Document.InsertMode = value; } } /// /// キャレットの点滅間隔 /// public int CaretBlinkTime { get; set; } /// /// 挿入モード時のキャレットの幅 /// public double CaretWidthOnInsertMode { get; set; } /// /// フォーカスがあるなら真をセットする /// public bool IsFocused { get; set; } /// /// キャレットを点滅させるなら真。そうでないなら偽 /// /// キャレット点滅タイマーもリセットされます public bool CaretBlink { get { return this._CaretBlink; } set { this._CaretBlink = value; if (value) this.tickCount = DateTime.Now.Ticks + this.To100nsTime(this.CaretBlinkTime); } } /// /// 一ページの高さに収まる行数を返す(こちらは表示されていない行も含みます) /// public int LineCountOnScreenWithInVisible { get; private set; } /// /// スクロール時に確保するマージン幅 /// public double ScrollMarginWidth { get { return this.PageBound.Width * 20 / 100; } } /// /// キャレットがある領域を示す /// public Point CaretLocation { get; private set; } /// /// ヒットテストを行う /// /// x座標 /// y座標 /// テキストエリア内にあれば真。そうでなければ偽 public bool HitTextArea(double x, double y) { if (x >= this.render.TextArea.X && x <= this.render.TextArea.Right && y >= this.render.TextArea.Y && y <= this.render.TextArea.Bottom) return true; else return false; } public bool IsUpperTextArea(double x, double y) { if (x >= this.render.TextArea.X && x <= this.render.TextArea.Right && y < this.render.TextArea.Y) return true; else return false; } public bool IsUnderTextArea(double x,double y) { if (x >= this.render.TextArea.X && x <= this.render.TextArea.Right && y > this.render.TextArea.Bottom) return true; else return false; } /// /// ヒットテストを行う /// /// x座標 /// 行 /// ヒットした場合はFoldingDataオブジェクトが返され、そうでない場合はnullが返る public FoldingItem HitFoldingData(double x, int row) { IEditorRender render = (IEditorRender)base.render; if (x >= this.GetRealtiveX(AreaType.FoldingArea) && x <= this.GetRealtiveX(AreaType.FoldingArea) + render.FoldingWidth) { int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row); int lineLength = this.LayoutLines.GetLengthFromLineNumber(row); FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineHeadIndex, lineLength); if (foldingData != null && foldingData.IsFirstLine(this.LayoutLines,row)) return foldingData; } return null; } /// /// Rectで指定された範囲にドキュメントを描く /// /// 描写する範囲 /// キャッシュした内容を使用しない場合は真を指定する /// 描写する範囲がPageBoundより小さい場合、キャッシュされた内容を使用することがあります。なお、レタリング後にrender.CacheContent()を呼び出さなかった場合は更新範囲にかかわらずキャッシュを使用しません public override void Draw(Rectangle updateRect,bool force = false) { if (this.LayoutLines.Count == 0) return; IEditorRender render = (IEditorRender)base.render; if ((updateRect.Height < this.PageBound.Height || updateRect.Width < this.PageBound.Width) && render.IsVaildCache() && !force) { render.DrawCachedBitmap(updateRect); } else { Rectangle background = this.PageBound; render.FillBackground(background); if (this.Document.HideRuler == false) this.DrawRuler(); this.DrawLineMarker(this.Document.CaretPostion.row); Point pos = this.render.TextArea.TopLeft; //画面上では行をずらして表示する pos.Y += this.Src.OffsetY; double endposy = this.render.TextArea.Bottom; Size lineNumberSize = new Size(this.render.LineNemberWidth, this.render.TextArea.Height); this.render.BeginClipRect(new Rectangle(this.PageBound.X, this.render.TextArea.Y, this.PageBound.Width, this.render.TextArea.Height)); //パフォーマンス向上のため行番号などを先に描く for (int i = this.Src.Row; i < this.LayoutLines.Count; i++) { int lineIndex = this.LayoutLines.GetIndexFromLineNumber(i); int lineLength = this.LayoutLines.GetLengthFromLineNumber(i); ITextLayout layout = this.LayoutLines.GetLayout(i); if (pos.Y > endposy) break; FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineIndex, lineLength); if (foldingData != null) { if (this.LayoutLines.FoldingCollection.IsHidden(lineIndex)) continue; if (foldingData.IsFirstLine(this.LayoutLines, i) && foldingData.End >= lineIndex + lineLength) render.DrawFoldingMark(foldingData.Expand, this.PageBound.X + this.GetRealtiveX(AreaType.FoldingArea), pos.Y); } if (this.Document.DrawLineNumber) { this.render.DrawString((i + 1).ToString(), this.PageBound.X + this.GetRealtiveX(AreaType.LineNumberArea), pos.Y, StringAlignment.Right, lineNumberSize,StringColorType.LineNumber); } DrawUpdateArea(i, pos.Y); pos.Y += layout.Height; //pos.Y += this.render.emSize.Height; } this.render.EndClipRect(); //リセットしないと行が正しく描けない pos = this.render.TextArea.TopLeft; pos.X -= this.Src.X; pos.Y += this.Src.OffsetY; this.render.BeginClipRect(this.render.TextArea); for (int i = this.Src.Row; i < this.LayoutLines.Count; i++) { int lineIndex = this.LayoutLines.GetIndexFromLineNumber(i); int lineLength = this.LayoutLines.GetLengthFromLineNumber(i); ITextLayout layout = this.LayoutLines.GetLayout(i); if (pos.Y > endposy) break; FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineIndex, lineLength); if (foldingData != null) { if (this.LayoutLines.FoldingCollection.IsHidden(lineIndex)) continue; } this.render.DrawOneLine(this.Document, this.LayoutLines, i, pos.X, pos.Y); pos.Y += layout.Height; } this.render.EndClipRect(); this.DrawInsertPoint(); this.Document.SelectGrippers.BottomLeft.Draw(this.render); this.Document.SelectGrippers.BottomRight.Draw(this.render); render.CacheContent(); } this.DrawCaret(); } void DrawUpdateArea(int row,double ypos) { IEditorRender render = (IEditorRender)base.render; if(this.LayoutLines.GetDirtyFlag(row)) { Point pos = new Point(this.PageBound.X + this.GetRealtiveX(AreaType.UpdateArea), ypos); Rectangle rect = new Rectangle(pos.X, pos.Y, UpdateAreaWidth, this.LayoutLines.GetLayout(row).Height); render.FillRectangle(rect, FillRectType.UpdateArea); } } void DrawRuler() { IEditorRender render = (IEditorRender)base.render; Point pos, from, to; Size emSize = render.emSize; Rectangle clipRect = this.render.TextArea; int count = 0; double markerHeight = emSize.Height / 2; if (this.Document.RightToLeft) { pos = new Point(clipRect.TopRight.X, clipRect.TopRight.Y - emSize.Height - LineMarkerThickness); for (; pos.X >= clipRect.TopLeft.X; pos.X -= emSize.Width, count++) { from = pos; to = new Point(pos.X, pos.Y + emSize.Height); int mod = count % 10; if (mod == 0) { string countStr = (count / 10).ToString(); double counterWidth = emSize.Width * countStr.Length; this.render.DrawString(countStr, pos.X - counterWidth, pos.Y, StringAlignment.Right, new Size(counterWidth, double.MaxValue)); } else if (mod == 5) from.Y = from.Y + emSize.Height / 2; else from.Y = from.Y + emSize.Height * 3 / 4; render.DrawLine(from, to); if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width) render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret); } } else { pos = new Point(clipRect.TopLeft.X, clipRect.TopLeft.Y - emSize.Height - LineMarkerThickness); for (; pos.X < clipRect.TopRight.X; pos.X += emSize.Width, count++) { from = pos; to = new Point(pos.X, pos.Y + emSize.Height); int mod = count % 10; if (mod == 0) this.render.DrawString((count / 10).ToString(), pos.X, pos.Y, StringAlignment.Left, new Size(double.MaxValue, double.MaxValue)); else if (mod == 5) from.Y = from.Y + emSize.Height / 2; else from.Y = from.Y + emSize.Height * 3 / 4; render.DrawLine(from, to); if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width) render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret); } } from = clipRect.TopLeft; from.Y -= LineMarkerThickness; to = clipRect.TopRight; to.Y -= LineMarkerThickness; render.DrawLine(from, to); } void DrawInsertPoint() { //一つしかない場合は行選択の可能性がある if (this.Selections.Count <= 1) return; IEditorRender render = (IEditorRender)base.render; foreach (Selection sel in this.Selections) { if (sel.length == 0) { TextPoint tp = this.GetLayoutLineFromIndex(sel.start); Point left = this.GetPostionFromTextPoint(tp); double lineHeight = render.emSize.Height; Rectangle InsertRect = new Rectangle(left.X, left.Y, CaretWidthOnInsertMode, lineHeight); render.FillRectangle(InsertRect, FillRectType.InsertPoint); } } } bool DrawCaret() { if (this.HideCaret || !this.IsFocused) return false; long diff = DateTime.Now.Ticks - this.tickCount; long blinkTime = this.To100nsTime(this.CaretBlinkTime); if (this.CaretBlink && diff % blinkTime >= blinkTime / 2) return false; Rectangle CaretRect = new Rectangle(); IEditorRender render = (IEditorRender)base.render; int row = this.Document.CaretPostion.row; ITextLayout layout = this.LayoutLines.GetLayout(row); double lineHeight = render.emSize.Height; double charWidth = layout.GetWidthFromIndex(this.Document.CaretPostion.col); if (this.InsertMode || charWidth == 0) { CaretRect.Size = new Size(CaretWidthOnInsertMode, lineHeight); CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y); render.FillRectangle(CaretRect, FillRectType.InsertCaret); } else { double height = lineHeight / 3; CaretRect.Size = new Size(charWidth, height); CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y + lineHeight - height); render.FillRectangle(CaretRect, FillRectType.OverwriteCaret); } return true; } long To100nsTime(int ms) { return ms * 10000; } public void DrawLineMarker(int row) { if (this.HideLineMarker || !this.IsFocused) return; IEditorRender render = (IEditorRender)base.render; Point p = this.CaretLocation; double height = this.LayoutLines.GetLineHeight(this.Document.CaretPostion); double width = this.render.TextArea.Width; render.FillRectangle(new Rectangle(this.PageBound.X + this.render.TextArea.X, this.CaretLocation.Y, width, height), FillRectType.LineMarker); } /// /// 現在のキャレット位置の領域を返す /// /// 矩形領域を表すRectangle public Rectangle GetCurrentCaretRect() { ITextLayout layout = this.LayoutLines.GetLayout(this.Document.CaretPostion.row); double width = layout.GetWidthFromIndex(this.Document.CaretPostion.col); if (width == 0.0) width = this.CaretWidthOnInsertMode; double height = this.LayoutLines.GetLineHeight(this.Document.CaretPostion); Rectangle updateRect = new Rectangle( this.CaretLocation.X, this.CaretLocation.Y, width, height); return updateRect; } /// /// 指定した座標の一番近くにあるTextPointを取得する /// /// テキストエリアを左上とする相対位置 /// 探索範囲 /// レイアウトラインを指し示すTextPoint public TextPoint GetTextPointFromPostion(Point p,TextPointSearchRange searchRange = TextPointSearchRange.TextAreaOnly) { if(searchRange == TextPointSearchRange.TextAreaOnly) { if (p.Y < this.render.TextArea.TopLeft.Y || p.Y > this.render.TextArea.BottomRight.Y) return TextPoint.Null; } TextPoint tp = new TextPoint(); if (this.LayoutLines.Count == 0) return tp; //表示領域から探索を始めるのでパディングの分だけ引く p.Y -= this.render.TextArea.Y; //p.Y に最も近い行を調べる var t = this.GetNearstRowAndOffsetY(this.Src.Row, p.Y); double relX = 0, relY; if (t == null) { if(p.Y > 0) { tp.row = this.LayoutLines.Count - 1; relY = this.LayoutLines.GetLayout(tp.row).Height - this.render.emSize.Height; } else if(p.Y == 0) { tp.row = this.Src.Row; relY = 0; } else { tp.row = 0; relY = 0; } } else { tp.row = t.Item1; relY = t.Item2; //相対位置がマイナスなので反転させる } if (searchRange == TextPointSearchRange.TextAreaOnly) { if (p.X < this.render.TextArea.X) return tp; } relX = p.X - this.render.TextArea.X; tp.col = this.LayoutLines.GetLayout(tp.row).GetIndexFromPostion(relX,relY); int lineLength = this.LayoutLines.GetLengthFromLineNumber(tp.row); if (tp.col > lineLength) tp.col = lineLength; return tp; } /// /// TextPointに対応する座標を得る /// /// レイアウトライン上の位置 /// テキストエリアを左上とする相対位置 public Point GetPostionFromTextPoint(TextPoint tp) { Point p = new Point(); for (int i = this.Src.Row; i < tp.row; i++) { int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i); int lineLength = this.LayoutLines.GetLengthFromLineNumber(i); if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex)) continue; p.Y += this.LayoutLines.GetLayout(i).Height; } Point relP = this.LayoutLines.GetLayout(tp.row).GetPostionFromIndex(tp.col); p.X += relP.X - Src.X + this.render.TextArea.X; p.Y += this.render.TextArea.Y + relP.Y; return p; } public Gripper HitGripperFromPoint(Point p) { if (this.Document.SelectGrippers.BottomLeft.IsHit(p)) return this.Document.SelectGrippers.BottomLeft; if (this.Document.SelectGrippers.BottomRight.IsHit(p)) return this.Document.SelectGrippers.BottomRight; return null; } public Rectangle GetRectFromIndex(int index,int width,int height) { TextPoint tp = this.LayoutLines.GetTextPointFromIndex(index); return this.GetRectFromTextPoint(tp, width, height); } public Rectangle GetRectFromTextPoint(TextPoint tp, int width, int height) { if (tp.row < this.Src.Row) return Rectangle.Empty; //画面外にある時は計算する必要がそもそもない if (tp.row - this.Src.Row > this.LineCountOnScreen) return Rectangle.Empty; double radius = width / 2; Point point = this.GetPostionFromTextPoint(tp); double lineHeight = this.render.emSize.Height; double srcOffsetY = this.Src.OffsetY; //画面上ではずれているので引く必要がある Rectangle rect = new Rectangle(point.X - radius, point.Y + lineHeight - srcOffsetY, width, height); if (rect.BottomLeft.Y >= this.render.TextArea.BottomLeft.Y || rect.BottomRight.X < this.render.TextArea.BottomLeft.X || rect.BottomLeft.X > this.render.TextArea.BottomRight.X) return Rectangle.Empty; return rect; } /// /// キャレットを指定した位置に移動させる /// /// /// /// 折り畳みを展開するなら真 public void JumpCaret(int row, int col, bool autoExpand = true) { if (autoExpand) { int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row); int lineLength = this.LayoutLines.GetLengthFromLineNumber(row); FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineHeadIndex, lineLength); if(foldingData != null) { if (this.LayoutLines.FoldingCollection.IsParentHidden(foldingData) || !foldingData.IsFirstLine(this.LayoutLines, row)) { this.LayoutLines.FoldingCollection.Expand(foldingData); } } } //イベント呼び出しの再入防止のため this.Document.SetCaretPostionWithoutEvent(new TextPoint(row, col)); } /// /// index上の文字が表示されるようにSrcを調整する /// /// インデックス /// 調整されたら真。そうでなければ偽 public bool AdjustSrc(int index) { TextPoint startTextPoint = this.GetLayoutLineFromIndex(index); Point relP = this.LayoutLines.GetLayout(startTextPoint.row).GetPostionFromIndex(startTextPoint.col); if (relP.X < this.Src.X || relP.X > this.Src.X + this.PageBound.Width) { this.TryScroll(relP.X, this.Src.Row); return true; } if (startTextPoint.row < this.Src.Row || startTextPoint.row > this.Src.Row + this.LineCountOnScreenWithInVisible) { this.TryScroll(this.Src.X, startTextPoint.row, relP.Y); return true; } return false; } /// /// キャレットがあるところまでスクロールする /// /// 再描写する必要があるなら真を返す /// Document.Update(type == UpdateType.Clear)イベント時に呼び出した場合、例外が発生します public bool AdjustCaretAndSrc(AdjustFlow flow = AdjustFlow.Both) { IEditorRender render = (IEditorRender)base.render; if (this.PageBound.Width == 0 || this.PageBound.Height == 0) { this.SetCaretPostion(this.Padding.Left + render.FoldingWidth, 0); return false; } bool result = false; TextPoint tp = this.Document.CaretPostion; double x = this.CaretLocation.X; double y = this.CaretLocation.Y; Point relPoint = this.LayoutLines.GetLayout(tp.row).GetPostionFromIndex(tp.col); if (flow == AdjustFlow.Col || flow == AdjustFlow.Both) { x = relPoint.X; double left = this.Src.X; double right = this.Src.X + this.render.TextArea.Width; if (x >= left && x <= right) //xは表示領域にないにある { x -= left; } else if (x > right) //xは表示領域の右側にある { this.Document.Src = new SrcPoint(x - this.render.TextArea.Width + this.ScrollMarginWidth,this.Document.Src.Row,this.Document.Src.OffsetY); if (this.Document.RightToLeft && this.Document.Src.X > 0) { System.Diagnostics.Debug.Assert(x > 0); this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Document.Src.OffsetY); } else { x = this.render.TextArea.Width - this.ScrollMarginWidth; } result = true; } else if (x < left) //xは表示領域の左側にある { this.Document.Src = new SrcPoint(x - this.ScrollMarginWidth, this.Document.Src.Row, this.Document.Src.OffsetY); if (!this.Document.RightToLeft && this.Document.Src.X < this.render.TextArea.X) { this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Document.Src.OffsetY); } else { x = this.ScrollMarginWidth; } result = true; } x += this.render.TextArea.X; } if (flow == AdjustFlow.Row || flow == AdjustFlow.Both) { int PhyLineCountOnScreen = (int)(this.render.TextArea.Height / this.render.emSize.Height); //計算量を減らすため if (tp.row < this.Src.Row || this.Src.Row + PhyLineCountOnScreen * 2 < tp.row) this.Document.Src = new SrcPoint(this.Src.X, tp.row, -relPoint.Y); //キャレットのY座標を求める double lineHeight = this.render.emSize.Height; double caret_y = this.Src.OffsetY; //src.rowからキャレット位置 double alignedHeight = PhyLineCountOnScreen * lineHeight - lineHeight; for (int i = this.Src.Row; i < tp.row; i++) { int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i); int lineLength = this.LayoutLines.GetLengthFromLineNumber(i); if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex)) continue; caret_y += this.LayoutLines.GetLayout(i).Height; } caret_y += relPoint.Y; if (caret_y < 0) { this.Document.Src = new SrcPoint(this.Src.X, tp.row, -relPoint.Y); y = 0; } else if (caret_y >= 0 && caret_y < alignedHeight) { y = caret_y; } else if(caret_y >= alignedHeight) { double caretYFromTextArea = Math.Min(caret_y - alignedHeight, alignedHeight - lineHeight); var newsrc = this.GetNearstRowAndOffsetY(tp.row, -caretYFromTextArea); if(newsrc == null) this.Document.Src = new SrcPoint(this.Src.X, tp.row, 0); else this.Document.Src = new SrcPoint(this.Src.X, newsrc.Item1, -newsrc.Item2); y = caretYFromTextArea; } y += this.render.TextArea.Y; result = true; } this.SetCaretPostion(x, y); if (result) { this.OnSrcChanged(null); } return result; } /// /// レイアウト行をテキストポイントからインデックスに変換する /// /// テキストポイント表す /// インデックスを返す public int GetIndexFromLayoutLine(TextPoint tp) { return this.LayoutLines.GetIndexFromTextPoint(tp); } /// /// インデックスからレイアウト行を指し示すテキストポイントに変換する /// /// インデックスを表す /// テキストポイント返す public TextPoint GetLayoutLineFromIndex(int index) { return this.LayoutLines.GetTextPointFromIndex(index); } /// /// 指定した座標までスクロールする /// /// /// /// /// 範囲外の座標を指定した場合、範囲内に収まるように調整されます /// public void Scroll(double x, int row) { if (x < 0) x = 0; if(row < 0) row = 0; int endRow = this.LayoutLines.Count - 1 - this.LineCountOnScreen; if (endRow < 0) endRow = 0; if (row > endRow) row = endRow; base.TryScroll(x, row); } /// /// 指定行までスクロールする /// /// 行 /// 指定行を画面上に置くなら真。そうでないなら偽 public void ScrollIntoView(int row, bool alignTop) { this.Scroll(0, row); if (alignTop) return; double y = this.render.TextArea.Height; for (int i = row; i >= 0; i--) { int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i); int lineLength = this.LayoutLines.GetLengthFromLineNumber(i); double height = this.LayoutLines.GetLayout(i).Height; if (y - height <= 0) { this.Scroll(0, i); } if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex)) continue; y -= height; } } public int AdjustRow(int row, bool isMoveNext) { if (this.LayoutLines.FoldingStrategy == null) return row; int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row); int lineLength = this.LayoutLines.GetLengthFromLineNumber(row); FoldingItem foldingData = this.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength); if (foldingData != null && !foldingData.Expand) { if (foldingData.End == this.Document.Length) return row; if (isMoveNext && lineHeadIndex > foldingData.Start) row = this.LayoutLines.GetLineNumberFromIndex(foldingData.End) + 1; else row = this.LayoutLines.GetLineNumberFromIndex(foldingData.Start); if(row > this.LayoutLines.Count - 1) row = this.LayoutLines.GetLineNumberFromIndex(foldingData.Start); } return row; } protected override void CalculateClipRect() { IEditorRender render = (IEditorRender)base.render; double x, y, width, height; if (this.Document.DrawLineNumber) { if (this.Document.RightToLeft) x = this.Padding.Left; else x = this.Padding.Left + UpdateAreaTotalWidth + this.render.LineNemberWidth + this.LineNumberMargin + render.FoldingWidth; width = this.PageBound.Width - this.render.LineNemberWidth - this.LineNumberMargin - this.Padding.Left - this.Padding.Right - render.FoldingWidth - UpdateAreaTotalWidth; } else { if (this.Document.RightToLeft) x = this.Padding.Left; else x = this.Padding.Left + UpdateAreaTotalWidth + render.FoldingWidth; width = this.PageBound.Width - this.Padding.Left - this.Padding.Right - render.FoldingWidth - UpdateAreaTotalWidth; } y = this.Padding.Top; height = this.PageBound.Height - this.Padding.Top - this.Padding.Bottom; if (this.Document.HideRuler == false) { double rulerHeight = this.render.emSize.Height + LineMarkerThickness; y += rulerHeight; height -= rulerHeight; } if (width < 0) width = 0; if (height < 0) height = 0; this.render.TextArea = new Rectangle(x, y, width, height); this.LineBreakingMarginWidth = width * 5 / 100; } public override void CalculateLineCountOnScreen() { if (this.LayoutLines.Count == 0 || this.PageBound.Height == 0) return; double y = 0; int i = this.Src.Row; int visualCount = this.Src.Row; for (; true; i++) { int row = i < this.LayoutLines.Count ? i : this.LayoutLines.Count - 1; int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(row); int lineLength = this.LayoutLines.GetLengthFromLineNumber(row); if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex) && row < this.LayoutLines.Count - 1) continue; ITextLayout layout = this.LayoutLines.GetLayout(row); double width = layout.Width; if (width > this._LongestWidth) this._LongestWidth = width; double lineHeight = layout.Height; y += lineHeight; if (y >= this.render.TextArea.Height) break; visualCount++; } this.LineCountOnScreen = Math.Max(visualCount - this.Src.Row - 1, 0); this.LineCountOnScreenWithInVisible = Math.Max(i - this.Src.Row - 1, 0); } void SetCaretPostion(double x, double y) { this.CaretLocation = new Point(x + this.PageBound.X, y + this.PageBound.Y); } void FoldingCollection_StatusChanged(object sender, FoldingItemStatusChangedEventArgs e) { this.CalculateLineCountOnScreen(); } enum AreaType { UpdateArea, FoldingArea, LineNumberArea, TextArea } double GetRealtiveX(AreaType type) { IEditorRender render = (IEditorRender)base.render; switch (type) { case AreaType.UpdateArea: if (this.Document.RightToLeft) return this.PageBound.TopRight.X - UpdateAreaTotalWidth; if (this.Document.DrawLineNumber) return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin - render.FoldingWidth - UpdateAreaTotalWidth; else return this.render.TextArea.X - render.FoldingWidth - UpdateAreaTotalWidth; case AreaType.FoldingArea: if (this.Document.RightToLeft) return this.PageBound.TopRight.X - render.FoldingWidth; if (this.Document.DrawLineNumber) return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin - render.FoldingWidth; else return this.render.TextArea.X - render.FoldingWidth; case AreaType.LineNumberArea: if (this.Document.DrawLineNumber == false) throw new InvalidOperationException(); if (this.Document.RightToLeft) return this.PageBound.TopRight.X - UpdateAreaTotalWidth - render.FoldingWidth - this.render.LineNemberWidth; else return this.render.TextArea.X - this.render.LineNemberWidth - this.LineNumberMargin; case AreaType.TextArea: return this.render.TextArea.X; } throw new ArgumentOutOfRangeException(); } } }