namespace Terminal.Gui.App;
internal partial class ApplicationImpl
{
///
public event EventHandler>? ScreenChanged;
///
public Rectangle Screen
{
get => Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
set
{
if (value is { } && (value.X != 0 || value.Y != 0))
{
throw new NotImplementedException ("Screen locations other than 0, 0 are not yet supported");
}
Driver?.SetScreenSize (value.Width, value.Height);
}
}
///
public bool ClearScreenNextIteration { get; set; }
///
public bool PositionCursor ()
{
if (Driver is null)
{
return false;
}
// Find the most focused view and position the cursor there.
View? mostFocused = Navigation?.GetFocused ();
// If the view is not visible or enabled, don't position the cursor
if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
{
var current = CursorVisibility.Invisible;
Driver?.GetCursorVisibility (out current);
if (current != CursorVisibility.Invisible)
{
Driver?.SetCursorVisibility (CursorVisibility.Invisible);
}
return false;
}
// If the view is not visible within it's superview, don't position the cursor
Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
Rectangle superViewViewport =
mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen;
if (!superViewViewport.IntersectsWith (mostFocusedViewport))
{
return false;
}
Point? cursor = mostFocused.PositionCursor ();
Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
if (cursor is { })
{
// Convert cursor to screen coords
cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location;
// If the cursor is not in a visible location in the SuperView, hide it
if (!superViewViewport.Contains (cursor.Value))
{
if (currentCursorVisibility != CursorVisibility.Invisible)
{
Driver.SetCursorVisibility (CursorVisibility.Invisible);
}
return false;
}
// Show it
if (currentCursorVisibility == CursorVisibility.Invisible)
{
Driver.SetCursorVisibility (mostFocused.CursorVisibility);
}
return true;
}
if (currentCursorVisibility != CursorVisibility.Invisible)
{
Driver.SetCursorVisibility (CursorVisibility.Invisible);
}
return false;
}
///
/// INTERNAL: Called when the application's screen has changed.
/// Raises the event.
///
/// The new screen size and position.
private void RaiseScreenChangedEvent (Rectangle screen)
{
//Screen = new (Point.Empty, screen.Size);
ScreenChanged?.Invoke (this, new (screen));
foreach (SessionToken t in SessionStack!)
{
if (t.Runnable is View runnableView)
{
runnableView.SetNeedsLayout ();
}
}
}
private void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { RaiseScreenChangedEvent (new (new (0, 0), e.Size!.Value)); }
///
public void LayoutAndDraw (bool forceRedraw = false)
{
if (ClearScreenNextIteration)
{
forceRedraw = true;
ClearScreenNextIteration = false;
}
if (forceRedraw)
{
Driver?.ClearContents ();
}
List views = [.. SessionStack!.Select (r => r.Runnable! as View)!];
if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
{
visiblePopover.SetNeedsDraw ();
visiblePopover.SetNeedsLayout ();
views.Insert (0, visiblePopover);
}
// Layout
bool neededLayout = View.Layout (views.ToArray ().Reverse ()!, Screen.Size);
// Draw
bool needsDraw = forceRedraw || views.Any (v => v is { NeedsDraw: true } or { SubViewNeedsDraw: true });
if (Driver is { } && (neededLayout || needsDraw))
{
Logging.Redraws.Add (1);
Driver.Clip = new (Screen);
// Only force a complete redraw if needed (needsLayout or forceRedraw).
// Otherwise, just redraw views that need it.
View.Draw (views: views.ToArray ().Cast (), neededLayout || forceRedraw);
Driver.Clip = new (Screen);
// Cause the driver to flush any pending updates to the terminal
Driver?.Refresh ();
}
}
}