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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ for every approved plan. Desktop's version follows the engine generation, so it
0.15.0.

### Added
- **Split view tells you which agent is which, and you rearrange it by dragging.** Each pane now
carries its own header naming the agent shown beneath it, so you read the name where the agent is
instead of matching it up from a strip along the top. Agents already on screen drop out of the tab
strip, leaving it as a list of what is *not* currently visible. Drag a tab onto a pane to put that
agent there, or drag one pane's header onto another to swap the two — each target says what the
drop will do before you release. With a single agent open, dragging another onto the chat area
starts a split, and which half you hover decides which side the dragged agent takes. The per-pane
dropdowns are gone; the tab menu still offers "Add to split view" for anyone who would rather not
drag.
- **Agents can talk to each other.** Typing `@` now offers the other open agents before project
files, so you can address one by name from another's conversation. An agent can check what another
is doing, read its conversation, ask it a question and get a real answer back, or hand it a whole
Expand Down
31 changes: 31 additions & 0 deletions src/MandoCode.Desktop/Controls/DragHandleGrid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml.Controls;

namespace MandoCode.Desktop.Controls;

/// <summary>
/// A <see cref="Grid"/> that shows the move cursor on hover, so something you can pick up and drop
/// elsewhere reads that way before you try it. Used as the content of agent tabs and pane headers,
/// both of which can be dragged into a split-view pane.
///
/// <para>Windows has no grab/grabbing cursor the way CSS does. <c>SizeAll</c> — the four-way arrow —
/// is its convention for "this object can be moved", and it is the closer match here:
/// <c>Hand</c> means "this is a link", which would say the wrong thing about a header whose click
/// selects rather than navigates.</para>
///
/// <para>A Grid rather than a Border, for the same reason <see cref="ResizeGrip"/> is: Border is
/// sealed, and <c>ProtectedCursor</c> is reachable only from a derived type. Since both headers
/// already hold their content in a Grid, the cursor rides on the content and covers everything but
/// the surrounding padding.</para>
///
/// <para>Carries the same hazard as ResizeGrip: assigning the cursor before the element is in the
/// visual tree fast-fails WinUI natively (STATUS_STOWED_EXCEPTION 0xC000027B). Loaded is the only
/// safe moment.</para>
/// </summary>
public sealed class DragHandleGrid : Grid
{
public DragHandleGrid()
{
Loaded += (_, _) => ProtectedCursor = InputSystemCursor.Create(InputSystemCursorShape.SizeAll);
}
}
23 changes: 20 additions & 3 deletions src/MandoCode.Desktop/MainWindow.History.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,14 +395,25 @@ private async void HistoryDeleteGroup_Click(object sender, RoutedEventArgs e)
// A Grid (not a StackPanel) so the label flexes and ellipsizes when the tab is narrow,
// while the badge and options button stay pinned at the right. LayoutTabStrip sets each
// header's Width; this just governs how that width is divided.
var row = new Grid { ColumnSpacing = 7 };
// Wider than the old 7 so the neutral gutter beside the badge and the options button is a
// little more forgiving — the cursor should have turned back to an arrow before the pointer
// reaches something clickable.
var row = new Grid { ColumnSpacing = 10 };
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
Grid.SetColumn(label, 0);

// Only the NAME carries the move cursor. The badge and the "..." button are things you
// click, and a move cursor over them would promise a drag that does not start there — the
// button swallows the press. ProtectedCursor applies to an element and its children, so the
// handle has to wrap just the label rather than the whole row.
var nameHandle = new Controls.DragHandleGrid();
nameHandle.Children.Add(label);

Grid.SetColumn(nameHandle, 0);
Grid.SetColumn(badge, 1);
Grid.SetColumn(options, 2);
row.Children.Add(label);
row.Children.Add(nameHandle);
row.Children.Add(badge);
row.Children.Add(options);

Expand All @@ -425,6 +436,12 @@ private void WireHeader(ChatTabEntry entry)
// on the header. Selecting first would be harmless anyway.
entry.Header.Tapped += (_, _) => SelectTab(entry);

// A tab is a drag source for the split view: pick it up and drop it on a pane to put that
// agent there. Harmless outside a split — nothing accepts the drop, so it simply ends.
entry.Header.CanDrag = true;
entry.Header.DragStarting += (_, args) => BeginAgentDrag(args, entry);
entry.Header.DropCompleted += (_, _) => EndAgentDrag();

var row = (Grid)entry.Header.Child;
var options = (Button)row.Children[^1];

Expand Down
Loading
Loading