//
// Authors:
// Miguel de Icaza (miguel@gnome.org)
//
// NOTE: Window is functionally identical to FrameView with the following exceptions.
// - Window is a Toplevel
// - FrameView Does not support padding (but should)
// - FrameView Does not support mouse dragging
// - FrameView Does not support IEnumerable
// Any udpates done here should probably be done in FrameView as well; TODO: Merge these classes
using System;
using System.Collections;
using NStack;
namespace Terminal.Gui {
///
/// A that draws a border around its with a at the top.
///
///
/// The 'client area' of a is a rectangle deflated by one or more rows/columns from . A this time there is no
/// API to determine this rectangle.
///
public class Window : Toplevel {
View contentView;
ustring title = ustring.Empty;
///
/// The title to be displayed for this window.
///
/// The title
public ustring Title {
get => title;
set {
if (!OnTitleChanging (title, value)) {
var old = title;
title = value;
if (Border != null) {
Border.Title = title;
}
OnTitleChanged (old, title);
}
SetNeedsDisplay ();
}
}
///
public override Border Border {
get => base.Border;
set {
if (base.Border != null && base.Border.Child != null && value.Child == null) {
value.Child = base.Border.Child;
}
base.Border = value;
if (value == null) {
return;
}
Rect frame;
if (contentView != null && (contentView.Width is Dim || contentView.Height is Dim)) {
frame = Rect.Empty;
} else {
frame = Frame;
}
AdjustContentView (frame);
Border.BorderChanged += Border_BorderChanged;
}
}
void Border_BorderChanged (Border border)
{
Rect frame;
if (contentView != null && (contentView.Width is Dim || contentView.Height is Dim)) {
frame = Rect.Empty;
} else {
frame = Frame;
}
AdjustContentView (frame);
}
///
/// ContentView is an internal implementation detail of Window. It is used to host Views added with .
/// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds
/// are actually deflated due to the border.
///
class ContentView : View {
Window instance;
public ContentView (Rect frame, Window instance) : base (frame)
{
Initialize (instance);
}
public ContentView (Window instance) : base ()
{
Initialize (instance);
}
private void Initialize (Window instance)
{
this.instance = instance;
CanFocus = this.instance.CanFocus;
Driver?.SetCursorVisibility (CursorVisibility.Invisible);
}
public override void OnCanFocusChanged ()
{
if (MostFocused == null && CanFocus && Visible) {
EnsureFocus ();
}
base.OnCanFocusChanged ();
}
public override bool OnMouseEvent (MouseEvent mouseEvent)
{
return instance.OnMouseEvent (mouseEvent);
}
}
///
/// Initializes a new instance of the class with an optional title using positioning.
///
/// Superview-relative rectangle specifying the location and size
/// Title
///
/// This constructor initializes a Window with a of . Use constructors
/// that do not take Rect parameters to initialize a Window with .
///
public Window (Rect frame, ustring title = null) : this (frame, title, padding: 0, border: null)
{
}
///
/// Initializes a new instance of the class with an optional title using positioning.
///
/// Title.
///
/// This constructor initializes a View with a of .
/// Use , , , and properties to dynamically control the size and location of the view.
///
public Window (ustring title = null) : this (title, padding: 0, border: null)
{
}
///
/// Initializes a new instance of the class using positioning.
///
public Window () : this (title: null) { }
///
/// Initializes a new instance of the using positioning with the specified frame for its location, with the specified frame padding,
/// and an optional title.
///
/// Superview-relative rectangle specifying the location and size
/// Title
/// Number of characters to use for padding of the drawn frame.
/// The .
///
/// This constructor initializes a Window with a of . Use constructors
/// that do not take Rect parameters to initialize a Window with of
///
public Window (Rect frame, ustring title = null, int padding = 0, Border border = null) : base (frame)
{
Initialize (title, frame, padding, border);
}
///
/// Initializes a new instance of the using positioning,
/// and an optional title.
///
/// Title.
/// Number of characters to use for padding of the drawn frame.
/// The .
///
/// This constructor initializes a View with a of .
/// Use , , , and properties to dynamically control the size and location of the view.
///
public Window (ustring title = null, int padding = 0, Border border = null) : base ()
{
Initialize (title, Rect.Empty, padding, border);
}
void Initialize (ustring title, Rect frame, int padding = 0, Border border = null)
{
CanFocus = true;
ColorScheme = Colors.Base;
if (title == null) title = ustring.Empty;
Title = title;
if (border == null) {
Border = new Border () {
BorderStyle = BorderStyle.Single,
Padding = new Thickness (padding),
Title = title
};
} else {
Border = border;
if (ustring.IsNullOrEmpty (border.Title)) {
border.Title = title;
}
}
AdjustContentView (frame);
}
void AdjustContentView (Rect frame)
{
var borderLength = Border.DrawMarginFrame ? 1 : 0;
var sumPadding = Border.GetSumThickness ();
var wp = new Point ();
var wb = new Size ();
if (frame == Rect.Empty) {
wp.X = borderLength + sumPadding.Left;
wp.Y = borderLength + sumPadding.Top;
wb.Width = borderLength + sumPadding.Right;
wb.Height = borderLength + sumPadding.Bottom;
if (contentView == null) {
contentView = new ContentView (this) {
X = wp.X,
Y = wp.Y,
Width = Dim.Fill (wb.Width),
Height = Dim.Fill (wb.Height)
};
} else {
contentView.X = wp.X;
contentView.Y = wp.Y;
contentView.Width = Dim.Fill (wb.Width);
contentView.Height = Dim.Fill (wb.Height);
}
} else {
wb.Width = (2 * borderLength) + sumPadding.Right + sumPadding.Left;
wb.Height = (2 * borderLength) + sumPadding.Bottom + sumPadding.Top;
var cFrame = new Rect (borderLength + sumPadding.Left, borderLength + sumPadding.Top, frame.Width - wb.Width, frame.Height - wb.Height);
if (contentView == null) {
contentView = new ContentView (cFrame, this);
} else {
contentView.Frame = cFrame;
}
}
if (Subviews?.Count == 0)
base.Add (contentView);
Border.Child = contentView;
}
/////
///// Enumerates the various s in the embedded .
/////
///// The enumerator.
//public new IEnumerator GetEnumerator ()
//{
// return contentView.GetEnumerator ();
//}
///
public override void Add (View view)
{
contentView.Add (view);
if (view.CanFocus) {
CanFocus = true;
}
AddMenuStatusBar (view);
}
///
public override void Remove (View view)
{
if (view == null) {
return;
}
SetNeedsDisplay ();
contentView.Remove (view);
if (contentView.InternalSubviews.Count < 1) {
CanFocus = false;
}
RemoveMenuStatusBar (view);
if (view != contentView && Focused == null) {
FocusFirst ();
}
}
///
public override void RemoveAll ()
{
contentView.RemoveAll ();
}
///
public override void Redraw (Rect bounds)
{
if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) {
Driver.SetAttribute (GetNormalColor ());
Clear ();
contentView.SetNeedsDisplay ();
}
var savedClip = contentView.ClipToBounds ();
// Redraw our contentView
contentView.Redraw (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded ? contentView.Bounds : bounds);
Driver.Clip = savedClip;
ClearLayoutNeeded ();
ClearNeedsDisplay ();
Driver.SetAttribute (GetNormalColor ());
Border.DrawContent (this, false);
}
///
public override void OnCanFocusChanged ()
{
if (contentView != null) {
contentView.CanFocus = CanFocus;
}
base.OnCanFocusChanged ();
}
///
/// The text displayed by the .
///
public override ustring Text {
get => contentView?.Text;
set {
base.Text = value;
if (contentView != null) {
contentView.Text = value;
}
}
}
///
/// Controls the text-alignment property of the label, changing it will redisplay the .
///
/// The text alignment.
public override TextAlignment TextAlignment {
get => contentView.TextAlignment;
set {
base.TextAlignment = contentView.TextAlignment = value;
}
}
///
/// An which allows passing a cancelable new value event.
///
public class TitleEventArgs : EventArgs {
///
/// The new Window Title.
///
public ustring NewTitle { get; set; }
///
/// The old Window Title.
///
public ustring OldTitle { get; set; }
///
/// Flag which allows cancelling the Title change.
///
public bool Cancel { get; set; }
///
/// Initializes a new instance of
///
/// The that is/has been replaced.
/// The new to be replaced.
public TitleEventArgs (ustring oldTitle, ustring newTitle)
{
OldTitle = oldTitle;
NewTitle = newTitle;
}
}
///
/// Called before the changes. Invokes the event, which can be cancelled.
///
/// The that is/has been replaced.
/// The new to be replaced.
/// `true` if an event handler cancelled the Title change.
public virtual bool OnTitleChanging (ustring oldTitle, ustring newTitle)
{
var args = new TitleEventArgs (oldTitle, newTitle);
TitleChanging?.Invoke (args);
return args.Cancel;
}
///
/// Event fired when the is changing. Set to
/// `true` to cancel the Title change.
///
public event Action TitleChanging;
///
/// Called when the has been changed. Invokes the event.
///
/// The that is/has been replaced.
/// The new to be replaced.
public virtual void OnTitleChanged (ustring oldTitle, ustring newTitle)
{
var args = new TitleEventArgs (oldTitle, newTitle);
TitleChanged?.Invoke (args);
}
///
/// Event fired after the has been changed.
///
public event Action TitleChanged;
}
}