Skip to content
Open
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
68 changes: 54 additions & 14 deletions FancyWM/Controls/TilingWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Windows;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

using FancyWM.Layouts.Tiling;
using FancyWM.ViewModels;

namespace FancyWM.Controls
Expand All @@ -25,9 +27,23 @@ public TilingWindowViewModel ViewModel
set => SetValue(ViewModelProperty, value);
}

// The overlay window never activates, so the menu gets no deactivation
// to close itself on. Close it once the cursor has left it instead.
private const int MenuCloseAfterMissedTicks = 2;
private readonly DispatcherTimer m_menuWatchTimer = new() { Interval = TimeSpan.FromMilliseconds(150) };
private int m_menuMissedTicks;

public TilingWindow()
{
InitializeComponent();
m_menuWatchTimer.Tick += OnMenuWatchTick;
MoreContextMenu.Opened += (_, _) => SetMenuOpen(ViewModel, true);
MoreContextMenu.Closed += (_, _) =>
{
m_menuWatchTimer.Stop();
SetMenuOpen(ViewModel, false);
};
Unloaded += (_, _) => MoreContextMenu.IsOpen = false;
}

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
Expand All @@ -37,31 +53,55 @@ protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
DataContext = ViewModel;
MoreContextMenu.IsOpen = false;
SetMenuOpen(e.OldValue as TilingWindowViewModel, false);
}
}

private static void SetMenuOpen(TilingWindowViewModel? viewModel, bool isOpen)
{
if (viewModel != null)
{
viewModel.IsMenuOpen = isOpen;
}
}

private void OnMoreClick(object sender, RoutedEventArgs e)
{
MoreContextMenu.IsOpen = true;
MoreContextMenu.DataContext = ViewModel;
var child = (UIElement)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(MoreContextMenu, 0), 0);
child.MouseEnter -= OnContextMenuMouseEnter;
child.MouseEnter += OnContextMenuMouseEnter;
m_menuMissedTicks = 0;
m_menuWatchTimer.Start();
}

private void OnContextMenuMouseEnter(object sender, MouseEventArgs e)
private void OnMenuWatchTick(object? sender, EventArgs e)
{
var child = (UIElement)sender;
child.MouseEnter -= OnContextMenuMouseEnter;
child.MouseLeave -= OnContextMenuMouseLeave;
child.MouseLeave += OnContextMenuMouseLeave;
if (IsCursorOver(MoreContextMenu) || IsCursorOver(MoreButton))
{
m_menuMissedTicks = 0;
return;
}

if (++m_menuMissedTicks >= MenuCloseAfterMissedTicks)
{
MoreContextMenu.IsOpen = false;
}
}

private void OnContextMenuMouseLeave(object sender, MouseEventArgs e)
/// <summary>
/// IsMouseOver is useless here: the open menu captures the mouse,
/// and WPF reports the capturing element as the one under the cursor.
/// </summary>
private bool IsCursorOver(FrameworkElement element)
{
var child = (UIElement)sender;
child.MouseLeave -= OnContextMenuMouseLeave;
MoreContextMenu.IsOpen = false;
if (ViewModel?.Node is not WindowNode node || PresentationSource.FromVisual(element) == null)
{
return false;
}

var cursor = node.WindowReference.Workspace.CursorLocation;
var local = element.PointFromScreen(new Point(cursor.X, cursor.Y));
return 0 <= local.X && local.X <= element.ActualWidth
&& 0 <= local.Y && local.Y <= element.ActualHeight;
}

bool m_canTriggerHorizontalGroup = true;
Expand Down
32 changes: 29 additions & 3 deletions FancyWM/ViewModels/TilingWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ private enum RevealState
public double RevealHighlightOpacity { get => m_revealHighlightOpacity; set => SetField(ref m_revealHighlightOpacity, value); }

public bool IsActionActive { get => m_isActionActive; set => SetField(ref m_isActionActive, value); }
/// <summary>
/// Keeps the actions visible while their "more options" menu is open.
/// </summary>
public bool IsMenuOpen
{
get => m_isMenuOpen;
set
{
if (m_isMenuOpen == value)
{
return;
}
SetField(ref m_isMenuOpen, value);
// The cursor may be resting far away by the time the menu closes.
if (!value && m_workspace != null)
{
UpdateActionsReveal(m_workspace.CursorLocation);
}
}
}
public bool IsPreviewVisible { get => m_isPreviewVisible; set => SetField(ref m_isPreviewVisible, value); }

private IWorkspace? m_workspace;
Expand All @@ -50,6 +70,7 @@ private enum RevealState
private bool m_isMoving = false;
private bool m_isPreviewVisible = false;
private bool m_isActionActive = false;
private bool m_isMenuOpen = false;
private WindowNode? m_currentNode;

public event RoutedEventHandler? BeginHorizontalSplitWith;
Expand Down Expand Up @@ -164,10 +185,15 @@ private void WindowReference_PositionChangeStart(object? sender, WindowPositionC
}

private void OnCursorLocationChanged(object? sender, CursorLocationChangedEventArgs e)
{
UpdateActionsReveal(e.NewLocation);
}

private void UpdateActionsReveal(WinMan.Point cursorLocation)
{
if (Node is WindowNode node)
{
if (m_isActionActive)
if (m_isActionActive || m_isMenuOpen)
{
RevealHighlightOpacity = 0;
ActionsVisibility = Visibility.Visible;
Expand All @@ -185,8 +211,8 @@ private void OnCursorLocationChanged(object? sender, CursorLocationChangedEventA

var windowPos = node.WindowReference.Position;

var x = e.NewLocation.X - windowPos.Left;
var y = e.NewLocation.Y - windowPos.Top;
var x = cursorLocation.X - windowPos.Left;
var y = cursorLocation.Y - windowPos.Top;

var isInBoundsX = 0 <= x && x <= windowPos.Width;

Expand Down
Loading