Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: quick action carousel/circular navigation #15

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
7 changes: 6 additions & 1 deletion src/HASS.Agent.Staging/HASS.Agent/Forms/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,15 @@ private async void ShowMain()
/// </summary>
private async void ShowQuickActions()
{

// check if quickactions aren't already open, and if there are any actions
if (HelperFunctions.CheckIfFormIsOpen("QuickActions"))
{
await HelperFunctions.TryBringToFront("QuickActions");
var quickActionsForm = HelperFunctions.GetForm("QuickActions") as QuickActions.QuickActions;
var result = await HelperFunctions.TryBringToFront(quickActionsForm);
if (result)
quickActionsForm.SelectNextQuickActionItem();

return;
}

Expand Down
218 changes: 154 additions & 64 deletions src/HASS.Agent.Staging/HASS.Agent/Forms/QuickActions/QuickActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace HASS.Agent.Forms.QuickActions
public partial class QuickActions : MetroForm
{
public event EventHandler ClearFocus;

private readonly List<QuickAction> _quickActions = new();
private readonly List<QuickActionPanelControl> _quickActionPanelControls = new();

Expand All @@ -26,7 +26,10 @@ public partial class QuickActions : MetroForm

public QuickActions(List<QuickAction> quickActions)
{
foreach (var quickAction in quickActions) _quickActions.Add(quickAction);
foreach (var quickAction in quickActions)
{
_quickActions.Add(quickAction);
}

InitializeComponent();
}
Expand Down Expand Up @@ -55,7 +58,11 @@ private async void QuickActions_Load(object sender, EventArgs e)

// check hass status
var hass = await CheckHassManagerAsync();
if (!hass) CloseWindow();
if (!hass)
CloseWindow();

// select first item
SelectQuickActionItem(0, 0);
}

/// <summary>
Expand All @@ -73,24 +80,33 @@ private void BuildLayout()

_columns = columns;
_rows = rows;

// prepare our panel
PnlActions.AutoSize = true;

for (var c = 0; c <= _columns; c++) PnlActions.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 152));
PnlActions.ColumnCount = _columns;

for (var r = 0; r <= _columns; r++) PnlActions.RowStyles.Add(new RowStyle(SizeType.Absolute, 255));
PnlActions.RowCount = _rows;

PnlActions.CellBorderStyle = TableLayoutPanelCellBorderStyle.None;


for (var c = 0; c <= _columns; c++)
{
PnlActions.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 152));
}

for (var r = 0; r <= _columns; r++)
{
PnlActions.RowStyles.Add(new RowStyle(SizeType.Absolute, 255));
}

// resize window
Width = 152 * columns + 20;
Height = 255 * rows + 30;

if (columns > 1) Width += 5 * (columns - 1);
if (rows > 1) Height += 5 * (rows - 1);
if (columns > 1)
Width += 5 * (columns - 1);
if (rows > 1)
Height += 5 * (rows - 1);

// add the quickactions as controls
var currentColumn = 0;
Expand All @@ -105,34 +121,57 @@ private void BuildLayout()
Row = currentRow
};

// update position when item is selected by mouse cursor
panelControl.QuickActionControl.MouseEnter += QuickActionItemMouseEnter;

// add to list
_quickActionPanelControls.Add(panelControl);

// store position
if (!_rowColumnCounts.ContainsKey(currentRow)) _rowColumnCounts.Add(currentRow, currentColumn);
else _rowColumnCounts[currentRow] = currentColumn;

if (!_rowColumnCounts.ContainsKey(currentRow))
{
_rowColumnCounts.Add(currentRow, currentColumn);
}
else
{
_rowColumnCounts[currentRow] = currentColumn;
}

// add to the panel
PnlActions.Controls.Add(quickAction, currentColumn, currentRow);

// set next column & row
if (currentColumn < columns - 1) currentColumn++;
if (currentColumn < columns - 1)
{
currentColumn++;
}
else
{
// on to the next row (if there is one)
currentColumn = 0;
if (currentRow < rows - 1) currentRow++;
if (currentRow < rows - 1)
currentRow++;
}
}
}

private void QuickActionItemMouseEnter(object sender, EventArgs e)
{
var position = PnlActions.GetPositionFromControl(sender as QuickActionControl);
_selectedColumn = position.Column;
_selectedRow = position.Row;
return;
}

/// <summary>
/// Tries to close the window
/// </summary>
internal void CloseWindow()
{
if (!IsHandleCreated) return;
if (IsDisposed) return;
if (!IsHandleCreated)
return;
if (IsDisposed)
return;

Invoke(new MethodInvoker(delegate
{
Expand Down Expand Up @@ -184,8 +223,10 @@ private async Task<bool> CheckHassManagerAsync()
/// <param name="loading"></param>
private void SetGuiLoading(bool loading)
{
if (!IsHandleCreated) return;
if (IsDisposed) return;
if (!IsHandleCreated)
return;
if (IsDisposed)
return;

Invoke(new MethodInvoker(delegate
{
Expand Down Expand Up @@ -216,6 +257,25 @@ private void QuickActions_FormClosing(object sender, FormClosingEventArgs e)
}
}

/// <summary>
/// Selects QuickAction item at given position
/// </summary>
/// <param name="msg"></param>
/// <param name="keyData"></param>
/// <returns></returns>
private bool SelectQuickActionItem(int row, int column)
{
var control = _quickActionPanelControls.Find(x => x.Row == row && x.Column == column);
if (control == null)
return false;

control.QuickActionControl.OnFocus();
_selectedColumn = column;
_selectedRow = row;

return true;
}

/// <summary>
/// Intercepts and processes the arrow keys
/// </summary>
Expand All @@ -226,83 +286,89 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
try
{
// if never pressed before ..
// should not happen, but select first item if nothing is selected
if (_selectedColumn == -1)
{
// .. always select first one
var control = _quickActionPanelControls.Find(x => x.Row == 0 && x.Column == 0);
if (control == null) return true;
SelectQuickActionItem(0, 0);

control.QuickActionControl.OnFocus();
_selectedColumn = 0;
_selectedRow = 0;
return true;
}

if (keyData == Keys.Down)
{
// is there a next row?
if (_selectedRow == _rows - 1) return true;

// jep, select the control below (or the last)
_selectedRow++;
var control = _quickActionPanelControls.Find(x => x.Row == _selectedRow && x.Column == _selectedColumn);
if (control == null)
// wrap up if we're at the last row
if (_selectedRow == _rows - 1)
{
// none found with same column, get the last
_selectedColumn = _rowColumnCounts[_selectedRow];
control = _quickActionPanelControls.Find(x => x.Row == _selectedRow && x.Column == _selectedColumn);
control?.QuickActionControl.OnFocus();
SelectQuickActionItem(0, _selectedColumn);

return true;
}

control.QuickActionControl.OnFocus();
var nextRow = _selectedRow + 1;
var selected = SelectQuickActionItem(nextRow, _selectedColumn);
if (!selected)
{
SelectQuickActionItem(nextRow, _rowColumnCounts[nextRow]);
}

return true;
}

if (keyData == Keys.Right)
{
// is there a next column?
var maxColumnsForRow = _rowColumnCounts[_selectedRow];
if (_selectedColumn == maxColumnsForRow) return true;

// jep, select the control to the right
_selectedColumn++;
var control = _quickActionPanelControls.Find(x => x.Row == _selectedRow && x.Column == _selectedColumn);
control?.QuickActionControl.OnFocus();
// wrap up to first row if there is nothing below
if (_selectedColumn == maxColumnsForRow)
{
var nextRow = _selectedRow == (_rows - 1) ? 0 : _selectedRow + 1;
SelectQuickActionItem(nextRow, 0);

return true;
}

SelectQuickActionItem(_selectedRow, _selectedColumn + 1);

return true;
}

if (keyData == Keys.Left)
{
// is there a previous column?
if (_selectedColumn == 0) return true;
// wrap up to last row if there is nothing above
if (_selectedColumn == 0)
{
var nextRow = _selectedRow == 0 ? _rows - 1 : _selectedRow - 1;
SelectQuickActionItem(nextRow, _rowColumnCounts[nextRow]);

return true;
}

SelectQuickActionItem(_selectedRow, _selectedColumn - 1);

// jep, select the control to the left
_selectedColumn--;
var control = _quickActionPanelControls.Find(x => x.Row == _selectedRow && x.Column == _selectedColumn);
control?.QuickActionControl.OnFocus();
return true;
}

if (keyData == Keys.Up)
{
// is there a previous row?
if (_selectedRow == 0) return true;
var nextRow = _selectedRow - 1;

// jep, select the control above (or the last)
_selectedRow--;
var control = _quickActionPanelControls.Find(x => x.Row == _selectedRow && x.Column == _selectedColumn);
if (control == null)
// wrap down if we're at the first row
if (_selectedRow == 0)
{
// none found with same column, get the first
_selectedColumn = 0;
control = _quickActionPanelControls.Find(x => x.Row == _selectedRow && x.Column == _selectedColumn);
control?.QuickActionControl.OnFocus();
nextRow = _rows - 1;
var maxColumnsForNextRow = _rowColumnCounts[nextRow];
var nextColumn = maxColumnsForNextRow < _selectedColumn ? maxColumnsForNextRow : _selectedColumn;
SelectQuickActionItem(nextRow, nextColumn);

return true;
}

control.QuickActionControl.OnFocus();
var selected = SelectQuickActionItem(nextRow, _selectedColumn);
if (!selected)
{
SelectQuickActionItem(nextRow, _rowColumnCounts[nextRow]);
}

return true;
}
}
Expand All @@ -315,6 +381,27 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
return base.ProcessCmdKey(ref msg, keyData);
}

/// <summary>
/// Selects next Quick Action item following right->down->up pattern
/// </summary>
public void SelectNextQuickActionItem()
{
var maxColumnsForRow = _rowColumnCounts[_selectedRow];

// are we at the end of the row / wrap up to first row if there is nothing below
if (_selectedColumn == maxColumnsForRow)
{
var nextRow = _selectedRow == (_rows - 1) ? 0 : _selectedRow + 1;
SelectQuickActionItem(nextRow, 0);

return;
}

SelectQuickActionItem(_selectedRow, _selectedColumn + 1);

return;
}

/// <summary>
/// Triggers clearing the focus of all controls
/// </summary>
Expand All @@ -323,9 +410,12 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData)

private void QuickActions_ResizeEnd(object sender, EventArgs e)
{
if (Variables.ShuttingDown) return;
if (!IsHandleCreated) return;
if (IsDisposed) return;
if (Variables.ShuttingDown)
return;
if (!IsHandleCreated)
return;
if (IsDisposed)
return;

try
{
Expand Down
Loading