public static bool Equals(Rect rect1, Rect rect2) { if (rect1.IsEmpty) { return rect2.IsEmpty; } return (((rect1.X.Equals(rect2.X) && rect1.Y.Equals(rect2.Y)) && rect1.Width.Equals(rect2.Width)) && rect1.Height.Equals(rect2.Height)); }
/// <summary> /// Maximizes the terminal window size and terminal buffer size. /// Current size is stored. /// </summary> public void Maximize( ) { if ( usingLinux ) { // Doesn't work in Konsole Console.Write ("\x1B[9;1t"); return; } if ( maximized ) return; // savedWindowRect = new Rect( new Point( Console.WindowLeft, Console.WindowTop ), new Size( Console.WindowWidth, Console.WindowHeight ) ); savedBufferSize = new Size(Console.BufferWidth, Console.BufferHeight); Win32.SendMessage(getConsoleWindowHwnd(), Win32.WM_SYSCOMMAND, Win32.SC_MAXIMIZE, IntPtr.Zero); int maxWidth = Console.LargestWindowWidth; int maxHeight = Console.LargestWindowHeight; Console.SetWindowPosition( 0, 0 ); Console.SetBufferSize(maxWidth, maxHeight); Console.SetWindowSize(maxWidth, maxHeight); // maximized = true; }
protected override Size ArrangeOverride(Size finalSize) { if ( null == content) return finalSize; Rect contentRect = new Rect(1, 1, Math.Max( 0, finalSize.Width - 2), Math.Max(0, finalSize.Height - 2)); content.Arrange( contentRect ); return finalSize; }
/// <summary> /// Копирует affectedRect из буфера на экран консоли с учетом того, что буфер /// находится на экране консоли по смещению offset. /// </summary> /// <param name="canvas"></param> /// <param name="affectedRect">Измененная область относительно this.</param> /// <param name="offset">В какой точке экрана размещен контрол (см <see cref="Renderer.RootElementRect"/>).</param> public void CopyToPhysicalCanvas(PhysicalCanvas canvas, Rect affectedRect, Point offset) { Rect rectToCopy = affectedRect; Rect bufferRect = new Rect(new Point(0, 0), new Size(this.width, this.height)); Rect canvasRect = new Rect(new Point(-offset.X, -offset.Y), canvas.Size); rectToCopy.Intersect(canvasRect); rectToCopy.Intersect(bufferRect); // for (int x = 0; x < rectToCopy.width; x++) { int bufferX = x + rectToCopy.x; int canvasX = x + rectToCopy.x + offset.x; for (int y = 0; y < rectToCopy.height; y++) { int bufferY = y + rectToCopy.y; int canvasY = y + rectToCopy.y + offset.y; CHAR_INFO charInfo = buffer[bufferX, bufferY]; canvas[canvasX][canvasY].Assign(charInfo); } } }
/// <summary> /// Оверлоад для оптимизированного наложения в случае, когда известно, что в дочернем /// контроле поменялась лишь часть, идентифицируемая параметром affectedRect. /// Будет обработана только эта часть дочернего контрола, и количество операций уменьшится. /// </summary> /// <param name="childBuffer"></param> /// <param name="actualOffset"></param> /// <param name="childRenderSize"></param> /// <param name="renderSlotRect"></param> /// <param name="layoutClip"></param> /// <param name="affectedRect">Прямоугольник в дочернем контроле, который был изменен.</param> public void ApplyChild(RenderingBuffer childBuffer, Vector actualOffset, Size childRenderSize, Rect renderSlotRect, Rect layoutClip, Rect? affectedRect) { // Считаем finalRect - прямоугольник относительно parent, который нужно закрасить Rect finalRect = layoutClip; if (affectedRect != null) finalRect.Intersect(affectedRect.Value); // Если child.RenderSlotRect больше child.RenderSize, а rendering buffer // дочернего контрола больше его RenderSize (такое бывает после уменьшения // размеров контрола - т.к. буфер может только увеличиваться, но не уменьшаться) - // то нам нужно либо передать в метод ApplyChild и child.RenderSize, либо // выполнить пересечение заранее finalRect.Intersect(new Rect(new Point(0, 0), childRenderSize)); // Because cannot call Offset() method of empty rect if ( finalRect.IsEmpty ) return; finalRect.Offset(actualOffset); finalRect.Intersect( renderSlotRect ); // Нужно также учесть размеры буфера текущего контрола finalRect.Intersect( new Rect(new Point(0, 0), new Size(this.width, this.height)) ); for ( int x = finalRect.Left; x < finalRect.Right; x++ ) { int parentX = x; int childX = parentX - actualOffset.x; for ( int y = finalRect.Top; y < finalRect.Bottom; y++ ) { int parentY = y; int childY = parentY - actualOffset.y; CHAR_INFO charInfo = childBuffer.buffer[childX, childY]; int opacity = childBuffer.opacityMatrix[childX, childY]; // Для полностью прозрачных пикселей родительского буфера - присваиваем и значение // пикселя, и значение opacity, дальше дело за следующим родителем if ( this.opacityMatrix[ parentX, parentY ] == 2 || this.opacityMatrix[ parentX, parentY ] == 6 ) { this.buffer[ parentX, parentY ] = charInfo; this.opacityMatrix[ parentX, parentY ] = opacity; } else { // В остальных случаях opacity родительского буфера остаётся, а // сам пиксель зависит от opacity дочернего элемента if ( opacity == 0 || opacity == 4 ) { this.buffer[ parentX, parentY ] = charInfo; } else if ( opacity == 1 || opacity == 5 ) { charInfo.Attributes = Colors.Blend( Color.DarkGray, Color.Black ); charInfo.UnicodeChar = buffer[ parentX, parentY ].UnicodeChar; buffer[ parentX, parentY ] = charInfo; } else if ( opacity == 3 || opacity == 7 ) { // берем фоновые атрибуты символа из родительского буфера Attr parentAttr = buffer[ parentX, parentY ].Attributes; if ( ( parentAttr & Attr.BACKGROUND_BLUE ) == Attr.BACKGROUND_BLUE ) { charInfo.Attributes |= Attr.BACKGROUND_BLUE; } else { charInfo.Attributes &= ~Attr.BACKGROUND_BLUE; } if ( ( parentAttr & Attr.BACKGROUND_GREEN ) == Attr.BACKGROUND_GREEN ) { charInfo.Attributes |= Attr.BACKGROUND_GREEN; } else { charInfo.Attributes &= ~Attr.BACKGROUND_GREEN; } if ( ( parentAttr & Attr.BACKGROUND_RED ) == Attr.BACKGROUND_RED ) { charInfo.Attributes |= Attr.BACKGROUND_RED; } else { charInfo.Attributes &= ~Attr.BACKGROUND_RED; } if ( ( parentAttr & Attr.BACKGROUND_INTENSITY ) == Attr.BACKGROUND_INTENSITY ) { charInfo.Attributes |= Attr.BACKGROUND_INTENSITY; } else { charInfo.Attributes &= ~Attr.BACKGROUND_INTENSITY; } buffer[ parentX, parentY ] = charInfo; } } } } }
protected override Size ArrangeOverride(Size finalSize) { if ( Content == null ) return finalSize; int width = finalSize.Width; int height = finalSize.Height; Rect finalRect = new Rect( new Point( -deltaX, -deltaY ), new Size( deltaX + Math.Max( 0, verticalScrollVisible ? width - 1 : width ), deltaY + Math.Max( 0, horizontalScrollVisible ? height - 1 : height ) ) ); // если мы сдвинули окно просмотра, а потом размеры, доступные контролу, увеличились, // мы должны вернуть дочерний контрол в точку (0, 0) if (deltaX > Content.DesiredSize.Width - Math.Max(0, verticalScrollVisible ? width - 1 : width)) { deltaX = 0; finalRect = new Rect(new Point(-deltaX, -deltaY), new Size( deltaX + Math.Max(0, verticalScrollVisible ? width - 1 : width), deltaY + Math.Max(0, horizontalScrollVisible ? height - 1 : height)) ); } if (deltaY > Content.DesiredSize.Height - Math.Max(0, horizontalScrollVisible ? height - 1 : height)) { deltaY = 0; finalRect = new Rect(new Point(-deltaX, -deltaY), new Size( deltaX + Math.Max(0, verticalScrollVisible ? width - 1 : width), deltaY + Math.Max(0, horizontalScrollVisible ? height - 1 : height)) ); } Content.Arrange( finalRect ); int resultWidth = Math.Min(verticalScrollVisible ? 1 + finalRect.Width : finalRect.Width, width); int resultHeight = Math.Min(horizontalScrollVisible ? 1 + finalRect.Height : finalRect.Height, height); Size result = new Size(resultWidth, resultHeight); return result; }
static Rect() { s_empty = CreateEmptyRect(); }
public static Rect Inflate(Rect rect, int width, int height) { rect.Inflate(width, height); return rect; }
public static Rect Offset(Rect rect, int offsetX, int offsetY) { rect.Offset(offsetX, offsetY); return rect; }
/// <summary> /// Получает для указанного контрола full render buffer и применяет его последовательно /// ко всем родительским элементам управления, вплоть до изображения на экране. /// Возвращает прямоугольник, необходимый для ревалидации на экране (affected rect). /// Учитывает Z-Order контролов-соседей (если родительский контрол имеет несколько дочерних, они могут перекрывать /// друг друга). /// Первый вызов производится с affectedRect = control.RenderSize. /// </summary> /// <returns>Affected rectangle in canvas should be copyied to console screen.</returns> private Rect applyChangesToCanvas(Control control, Rect affectedRect) { // если системой лайаута были определены размеры дочернего контрола, превышающие размеры слота // (такое может произойти, если дочерний контрол игнорирует переданные аргументы в MeasureOverride // и ArrangeOverride), то в этом месте может прийти affectedRect, выходящий за рамки // текущего RenderSize контрола, и мы должны выполнить intersection для корректного наложения affectedRect.Intersect(new Rect(new Point(0, 0), control.RenderSize)); RenderingBuffer fullBuffer = getOrCreateFullBufferForControl(control); if (control.Parent != null) { RenderingBuffer fullParentBuffer = getOrCreateFullBufferForControl(control.Parent); // если буфер контрола содержит opacity пиксели в affectedRect, то мы вынуждены переинициализировать // буфер парента целиком (не вызывая Render, конечно, но переналожением буферов дочерних элементов) if (fullBuffer.ContainsOpacity(affectedRect)) { fullParentBuffer.Clear(); fullParentBuffer.CopyFrom(getOrCreateBufferForControl(control.Parent)); foreach (Control child in control.Parent.Children) { if (child.Visibility == Visibility.Visible) { RenderingBuffer childBuffer = getOrCreateFullBufferForControl(child); fullParentBuffer.ApplyChild(childBuffer, child.ActualOffset, child.RenderSize, child.RenderSlotRect, child.LayoutClip); } } } if (control.Visibility == Visibility.Visible) { if (affectedRect == new Rect(new Point(0, 0), control.RenderSize)) { fullParentBuffer.ApplyChild(fullBuffer, control.ActualOffset, control.RenderSize, control.RenderSlotRect, control.LayoutClip); } else { fullParentBuffer.ApplyChild(fullBuffer, control.ActualOffset, control.RenderSize, control.RenderSlotRect, control.LayoutClip, affectedRect); } } // определим соседей контрола, которые могут перекрывать его IList<Control> neighbors = control.Parent.GetChildrenOrderedByZIndex(); // восстанавливаем изображение поверх обновленного контрола, если // имеются контролы, лежащие выше по z-order int controlIndex = neighbors.IndexOf(control); // начиная с controlIndex + 1 в списке лежат контролы с z-index больше чем z-index текущего контрола for (int i = controlIndex + 1; i < neighbors.Count; i++) { Control neighbor = neighbors[i]; fullParentBuffer.ApplyChild(getOrCreateFullBufferForControl(neighbor), neighbor.ActualOffset, neighbor.RenderSize, neighbor.RenderSlotRect, neighbor.LayoutClip); } Rect parentAffectedRect = control.RenderSlotRect; parentAffectedRect.Intersect(new Rect(affectedRect.x + control.ActualOffset.x, affectedRect.y + control.ActualOffset.y, affectedRect.width, affectedRect.height)); // нет смысла продолжать подъем вверх по дереву, если контрола точно уже не видно if (parentAffectedRect.IsEmpty) { return Rect.Empty; } return applyChangesToCanvas(control.Parent, parentAffectedRect); } else { if (control != RootElement) throw new InvalidOperationException("Assertion failed."); // мы добрались до экрана консоли fullBuffer.CopyToPhysicalCanvas(Canvas, affectedRect, RootElementRect.TopLeft); return affectedRect; } }
/// <summary> /// Сбрасывает все изменения, накопленные в течение предыдущих вызовов /// UpdateLayout, на экран. /// </summary> public void FinallyApplyChangesToCanvas( bool forceRepaintAll = false ) { Rect affectedRect = Rect.Empty; // Propagate updated rendered buffers to parent elements and eventually to Canvas foreach (Control control in renderingUpdatedControls) { Rect currentAffectedRect = applyChangesToCanvas(control, new Rect(new Point(0, 0), control.RenderSize)); affectedRect.Union(currentAffectedRect); } if ( forceRepaintAll ) { affectedRect = new Rect( rootElementRect.Size ); } // Flush stored image (with this.RootElementRect offset) if (!affectedRect.IsEmpty) { // Affected rect relative to canvas Rect affectedRectAbsolute = new Rect(affectedRect.x + RootElementRect.x, affectedRect.y + RootElementRect.y, affectedRect.width, affectedRect.height); // Clip according to real canvas size affectedRectAbsolute.Intersect(new Rect(new Point(0, 0), Canvas.Size)); Canvas.Flush(affectedRectAbsolute); } // If anything changed in layout - update displaying cursor state if (renderingUpdatedControls.Count > 0) { ConsoleApplication.Instance.FocusManager.RefreshMouseCursor(); } // Prepare for next layout pass renderingUpdatedControls.Clear(); }
private void runWindows(Control control) { this.mainControl = control; // stdInputHandle = Win32.GetStdHandle(StdHandleType.STD_INPUT_HANDLE); stdOutputHandle = Win32.GetStdHandle(StdHandleType.STD_OUTPUT_HANDLE); IntPtr[] handles = new[] { exitWaitHandle.SafeWaitHandle.DangerousGetHandle(), stdInputHandle, invokeWaitHandle.SafeWaitHandle.DangerousGetHandle( ) }; // Set console mode to enable mouse and window resizing events const uint ENABLE_WINDOW_INPUT = 0x0008; const uint ENABLE_MOUSE_INPUT = 0x0010; uint consoleMode; Win32.GetConsoleMode( stdInputHandle, out consoleMode ); Win32.SetConsoleMode(stdInputHandle, consoleMode | ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT); // Get console screen buffer size CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo; Win32.GetConsoleScreenBufferInfo( stdOutputHandle, out screenBufferInfo ); // Set Canvas size to current console window size (not to whole buffer size) savedWindowRect = new Rect( new Point( Console.WindowLeft, Console.WindowTop ), new Size( Console.WindowWidth, Console.WindowHeight ) ); CanvasSize = new Size(savedWindowRect.Width, savedWindowRect.Height); canvas = userCanvasSize.IsEmpty ? new PhysicalCanvas( screenBufferInfo.dwSize.X, screenBufferInfo.dwSize.Y, stdOutputHandle ) : new PhysicalCanvas( userCanvasSize.Width, userCanvasSize.Height, stdOutputHandle); renderer.Canvas = canvas; // Fill the canvas by default renderer.RootElementRect = userRootElementRect.IsEmpty ? new Rect( new Point(0, 0), canvas.Size ) : userRootElementRect; renderer.RootElement = mainControl; // mainControl.Invalidate(); renderer.UpdateLayout(); renderer.FinallyApplyChangesToCanvas( ); // Initially hide the console cursor HideCursor(); this.running = true; this.mainThreadId = Thread.CurrentThread.ManagedThreadId; // while (true) { // 100 ms instead of Win32.INFINITE to check console window Zoomed and Iconic // state periodically (because if user presses Maximize/Restore button // there are no input event generated). uint waitResult = Win32.WaitForMultipleObjects(3, handles, false, 100); if (waitResult == 0) { break; } if (waitResult == 1) { processInput(); } if ( waitResult == 2 ) { // Do nothing special - because invokeActions will be invoked in loop anyway } // If we received WAIT_TIMEOUT - check window Zoomed and Iconic state // and correct buffer size and console window size if ( waitResult == 0x00000102 ) { IntPtr consoleWindow = getConsoleWindowHwnd( ); bool isZoomed = Win32.IsZoomed(consoleWindow); bool isIconic = Win32.IsIconic(consoleWindow); if (maximized != isZoomed && !isIconic) { if (isZoomed) Maximize(); else Restore(); } if ( !maximized ) { savedWindowRect = new Rect( new Point( Console.WindowLeft, Console.WindowTop ), new Size( Console.WindowWidth, Console.WindowHeight ) ); } } // WAIT_FAILED if ( waitResult == 0xFFFFFFFF) { throw new InvalidOperationException("Invalid wait result of WaitForMultipleObjects."); } while ( true ) { bool anyInvokeActions = isAnyInvokeActions( ); bool anyRoutedEvent = !EventManager.IsQueueEmpty( ); bool anyLayoutToRevalidate = renderer.AnyControlInvalidated; if (!anyInvokeActions && !anyRoutedEvent && !anyLayoutToRevalidate) break; EventManager.ProcessEvents(); processInvokeActions( ); renderer.UpdateLayout( ); } renderer.FinallyApplyChangesToCanvas( ); } // Restore cursor visibility before exit ShowCursor(); // Restore console mode before exit Win32.SetConsoleMode( stdInputHandle, consoleMode ); renderer.RootElement = null; // todo : restore attributes of console output }
public void Run( Control control, Size canvasSize, Rect rectToUse ) { userCanvasSize = canvasSize; userRootElementRect = rectToUse; Run(control); }
public override void Flush( Rect affectedRect ) { flushScreen( ); //Console.WriteLine("Flush called"); }
public bool Equals(Rect value) { return Equals(this, value); }
public static Rect Offset(Rect rect, Vector offsetVector) { rect.Offset(offsetVector.X, offsetVector.Y); return rect; }
public bool Contains(Rect rect) { if (this.IsEmpty || rect.IsEmpty) { return false; } return ((((this.x <= rect.x) && (this.y <= rect.y)) && ((this.x + this.width) >= (rect.x + rect.width))) && ((this.y + this.height) >= (rect.y + rect.height))); }
public static Rect Inflate(Rect rect, Size size) { rect.Inflate(size.width, size.height); return rect; }
public bool IntersectsWith(Rect rect) { if (this.IsEmpty || rect.IsEmpty) { return false; } return ((((rect.Left <= this.Right) && (rect.Right >= this.Left)) && (rect.Top <= this.Bottom)) && (rect.Bottom >= this.Top)); }
private static Rect CreateEmptyRect() { Rect rect = new Rect { x = 0, y = 0, width = 0, height = 0 }; return rect; }
public void Intersect(Rect rect) { if (!this.IntersectsWith(rect)) { this = Empty; } else { int num = Math.Max(this.Left, rect.Left); int num2 = Math.Max(this.Top, rect.Top); this.width = Math.Max((Math.Min(this.Right, rect.Right) - num), 0); this.height = Math.Max((Math.Min(this.Bottom, rect.Bottom) - num2), 0); this.x = num; this.y = num2; } }
public Rect( Rect copy ) { this.x = copy.x; this.y = copy.y; this.width = copy.width; this.height = copy.height; }
public static Rect Intersect(Rect rect1, Rect rect2) { rect1.Intersect(rect2); return rect1; }
/// <summary> /// Writes collected data to console screen buffer, but paints specified rect only. /// </summary> public virtual void Flush(Rect affectedRect) { if (stdOutputHandle != IntPtr.Zero) { // we are in windows environment SMALL_RECT rect = new SMALL_RECT((short) affectedRect.x, (short) affectedRect.y, (short) (affectedRect.width + affectedRect.x), (short) (affectedRect.height + affectedRect.y)); if (!Win32.WriteConsoleOutputCore(stdOutputHandle, buffer, new COORD((short) size.Width, (short) size.Height), new COORD((short) affectedRect.x, (short) affectedRect.y), ref rect)) { throw new InvalidOperationException(string.Format("Cannot write to console : {0}", Win32.GetLastErrorMessage())); } } else { // we are in linux for (int i = 0; i < affectedRect.width; i++) { int x = i + affectedRect.x; for (int j = 0; j < affectedRect.height; j++) { int y = j + affectedRect.y; // todo : convert attributes and optimize rendering bool fgIntensity; short index = NCurses.winAttrsToNCursesAttrs(buffer[y, x].Attributes, out fgIntensity); if (fgIntensity) { NCurses.attrset( (int) (NCurses.COLOR_PAIR(index) | NCurses.A_BOLD)); } else { NCurses.attrset( (int) NCurses.COLOR_PAIR(index)); } char outChar = buffer[y, x].UnicodeChar != '\0' ? (buffer[y, x].UnicodeChar) : ' '; NCurses.mvaddstr(y, x, new string(outChar,1)); } } NCurses.refresh(); } }
public void Union(Rect rect) { if (this.IsEmpty) { this = rect; } else if (!rect.IsEmpty) { int num = Math.Min(this.Left, rect.Left); int num2 = Math.Min(this.Top, rect.Top); if ((rect.Width == int.MaxValue) || (this.Width == int.MaxValue)) { this.width = int.MaxValue; } else { int num3 = Math.Max(this.Right, rect.Right); this.width = Math.Max((num3 - num), 0); } if ((rect.Height == int.MaxValue) || (this.Height == int.MaxValue)) { this.height = int.MaxValue; } else { int num4 = Math.Max(this.Bottom, rect.Bottom); this.height = Math.Max((num4 - num2), 0); } this.x = num; this.y = num2; } }
/// <summary> /// Проверяет, содержит ли affectedRect пиксели с выставленным значением opacity. /// Это необходимо для обеспечения корректного смешивания с родительскими буферами в случае /// частичного обновления экрана (если это не учитывать, то состояние экрана может смешивать /// новые пиксели со старыми, которые были получены при предыдущем вызове рендеринга). /// </summary> public bool ContainsOpacity(Rect affectedRect) { for (int x = 0; x < affectedRect.width; x++) { for (int y = 0; y < affectedRect.height; y++) { if (opacityMatrix[x + affectedRect.x, y + affectedRect.y] != 0) { return true; } } } return false; }
public static Rect Union(Rect rect1, Rect rect2) { rect1.Union(rect2); return rect1; }
/// <summary> /// Накладывает буфер дочернего элемента на текущий. Дочерний буфер виртуально накладывается на текущий /// в соответствии с переданным actualOffset, а потом та часть дочернего буфера, которая попадает в /// renderSlotRect, прорисовывается. renderSlotRect определен отн-но текущего буфера (а не дочернего). /// layoutClip определяет, какая часть дочернего буфера будет прорисована в текущий буфер (клиппинг, /// возникающий при применении Margin и Alignment'ов). /// </summary> /// <param name="childBuffer"></param> /// <param name="actualOffset">Смещение буфера дочернего элемента относительно текущего.</param> /// <param name="childRenderSize">Размер отрендеренного дочерним элементом контента - может /// быть меньше размера childBuffer, поэтому нужно передавать явно.</param> /// <param name="renderSlotRect">Размер и положение слота, выделенного дочернему элементу.</param> /// <param name="layoutClip">Часть дочернего буфера, которая будет отрисована - по размеру может быть /// меньше или равна RenderSlotRect.Size. По координатам соотносится с childBuffer.</param> public void ApplyChild(RenderingBuffer childBuffer, Vector actualOffset, Size childRenderSize, Rect renderSlotRect, Rect layoutClip) { ApplyChild( childBuffer, actualOffset, childRenderSize, renderSlotRect, layoutClip, null ); }
public static Rect Union(Rect rect, Point point) { rect.Union(new Rect(point, point)); return rect; }