Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion Terminal.Gui/Views/TabView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ public class TabView : View {
/// </summary>
public event EventHandler<TabChangedEventArgs> SelectedTabChanged;


/// <summary>
/// Event fired when a <see cref="TabView.Tab"/> is clicked. Can be used to cancel navigation,
/// show context menu (e.g. on right click) etc.
/// </summary>
public event EventHandler<TabMouseEventArgs> TabClicked;


/// <summary>
/// The currently selected member of <see cref="Tabs"/> chosen by the user
/// </summary>
Expand Down Expand Up @@ -665,6 +673,22 @@ private int GetUnderlineYPosition ()

public override bool MouseEvent (MouseEvent me)
{
var hit = ScreenToTab (me.X, me.Y);

bool isClick = me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
me.Flags.HasFlag (MouseFlags.Button2Clicked) ||
me.Flags.HasFlag (MouseFlags.Button3Clicked);

if (isClick) {
host.OnTabClicked (new TabMouseEventArgs (hit, me));

// user canceled click
if (me.Handled) {
return true;
}
}


if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
!me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
!me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
Expand All @@ -689,7 +713,7 @@ public override bool MouseEvent (MouseEvent me)
return true;
}

var hit = ScreenToTab (me.X, me.Y);

if (hit != null) {
host.SelectedTab = hit;
SetNeedsDisplay ();
Expand Down Expand Up @@ -738,6 +762,45 @@ public Tab ScreenToTab (int x, int y)
}
}

/// <summary>
/// Raises the <see cref="TabClicked"/> event.
/// </summary>
/// <param name="tabMouseEventArgs"></param>
protected virtual private void OnTabClicked (TabMouseEventArgs tabMouseEventArgs)
{
TabClicked?.Invoke (this, tabMouseEventArgs);
}

/// <summary>
/// Describes a mouse event over a specific <see cref="TabView.Tab"/> in a <see cref="TabView"/>.
/// </summary>
public class TabMouseEventArgs : EventArgs {

/// <summary>
/// Gets the <see cref="TabView.Tab"/> (if any) that the mouse
/// was over when the <see cref="MouseEvent"/> occurred.
/// </summary>
/// <remarks>This will be null if the click is after last tab
/// or before first.</remarks>
public Tab Tab { get; }

/// <summary>
/// Gets the actual mouse event. Use <see cref="MouseEvent.Handled"/> to cancel this event
/// and perform custom behavior (e.g. show a context menu).
/// </summary>
public MouseEvent MouseEvent { get; }

/// <summary>
/// Creates a new instance of the <see cref="TabMouseEventArgs"/> class.
/// </summary>
/// <param name="tab"><see cref="TabView.Tab"/> that the mouse was over when the event occurred.</param>
/// <param name="mouseEvent">The mouse activity being reported</param>
public TabMouseEventArgs (Tab tab, MouseEvent mouseEvent)
{
Tab = tab;
MouseEvent = mouseEvent;
}
}

/// <summary>
/// A single tab in a <see cref="TabView"/>
Expand Down
42 changes: 40 additions & 2 deletions UICatalog/Scenarios/Notepad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public override void Setup ()
Height = Dim.Fill (1),
};

tabView.TabClicked += TabView_TabClicked;

tabView.Style.ShowBorder = true;
tabView.ApplyStyleChanges ();

Expand All @@ -63,14 +65,46 @@ public override void Setup ()
New ();
}

private void TabView_TabClicked (object sender, TabView.TabMouseEventArgs e)
{
// we are only interested in right clicks
if(!e.MouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked)) {
return;
}

MenuBarItem items;

if (e.Tab == null) {
items = new MenuBarItem (new MenuItem [] {
new MenuItem ($"Open", "", () => Open()),
});

} else {
items = new MenuBarItem (new MenuItem [] {
new MenuItem ($"Save", "", () => Save(e.Tab)),
new MenuItem ($"Close", "", () => Close(e.Tab)),
});
}


var contextMenu = new ContextMenu (e.MouseEvent.X + 1, e.MouseEvent.Y + 1, items);

contextMenu.Show ();
e.MouseEvent.Handled = true;
}

private void New ()
{
Open ("", null, $"new {numbeOfNewTabs++}");
}

private void Close ()
{
var tab = tabView.SelectedTab as OpenedFile;
Close (tabView.SelectedTab);
}
private void Close (TabView.Tab tabToClose)
{
var tab = tabToClose as OpenedFile;

if (tab == null) {
return;
Expand Down Expand Up @@ -158,7 +192,11 @@ private void Open (string initialText, FileInfo fileInfo, string tabName)

public void Save ()
{
var tab = tabView.SelectedTab as OpenedFile;
Save (tabView.SelectedTab);
}
public void Save (TabView.Tab tabToSave)
{
var tab = tabToSave as OpenedFile;

if (tab == null) {
return;
Expand Down
92 changes: 92 additions & 0 deletions UnitTests/Views/TabViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,98 @@ public void ShowTopLine_True_TabsOnBottom_True_With_Unicode ()
└──────────────┘ ", output);
}

[Fact, AutoInitShutdown]
public void MouseClick_ChangesTab ()
{
var tv = GetTabView (out var tab1, out var tab2, false);

tv.Width = 20;
tv.Height = 5;

tv.LayoutSubviews ();

tv.Redraw (tv.Bounds);

var tabRow = tv.Subviews[0];
Assert.Equal("TabRowView",tabRow.GetType().Name);

TestHelpers.AssertDriverContentsAre (@"
┌────┐
│Tab1│Tab2
│ └─────────────┐
│hi │
└──────────────────┘
", output);

TabView.Tab clicked = null;


tv.TabClicked += (s,e)=>{
clicked = e.Tab;
};

// Waving mouse around does not trigger click
for(int i=0;i<100;i++)
{
tabRow.MouseEvent(new MouseEvent{
X = i,
Y = 1,
Flags = MouseFlags.ReportMousePosition
});

Assert.Null(clicked);
Assert.Equal(tab1, tv.SelectedTab);
}

tabRow.MouseEvent(new MouseEvent{
X = 3,
Y = 1,
Flags = MouseFlags.Button1Clicked
});

Assert.Equal(tab1, clicked);
Assert.Equal(tab1, tv.SelectedTab);


// Click to tab2
tabRow.MouseEvent(new MouseEvent{
X = 7,
Y = 1,
Flags = MouseFlags.Button1Clicked
});

Assert.Equal(tab2, clicked);
Assert.Equal(tab2, tv.SelectedTab);

// cancel navigation
tv.TabClicked += (s,e)=>{
clicked = e.Tab;
e.MouseEvent.Handled = true;
};

tabRow.MouseEvent(new MouseEvent{
X = 3,
Y = 1,
Flags = MouseFlags.Button1Clicked
});

// Tab 1 was clicked but event handler blocked navigation
Assert.Equal(tab1, clicked);
Assert.Equal(tab2, tv.SelectedTab);


tabRow.MouseEvent (new MouseEvent {
X = 10,
Y = 1,
Flags = MouseFlags.Button1Clicked
});

// Clicking beyond last tab should raise event with null Tab
Assert.Null (clicked);
Assert.Equal (tab2, tv.SelectedTab);

}

private void InitFakeDriver ()
{
var driver = new FakeDriver ();
Expand Down