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
1 change: 1 addition & 0 deletions design/mockups/per-target-launch-options/APPROVED
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
2026-07-08 feat/launch-commands-editor approved 02-dark-pro.html
2026-07-08 fix/product-safety-review reused 02-dark-pro.html for non-visual drop safety changes
2026-07-09 fix/product-format-cleanup reused 02-dark-pro.html for non-visual dotnet format cleanup
36 changes: 29 additions & 7 deletions src/Dropwheel/Services/HotkeyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public static bool TryParse(string s, out uint mods, out uint vk)
foreach (var part in s.Split('+', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
switch (part.ToLowerInvariant())
{
case "alt": mods |= 0x1; break;
case "ctrl": mods |= 0x2; break;
case "alt": mods |= 0x1; break;
case "ctrl": mods |= 0x2; break;
case "shift": mods |= 0x4; break;
case "win": mods |= 0x8; break;
case "win": mods |= 0x8; break;
default:
if (key != null) return false; // more than one non-modifier key
try
Expand Down Expand Up @@ -74,10 +74,32 @@ private static string NormalizeKeyName(string part)

private static readonly Dictionary<char, char> CyrillicToLatin = new()
{
['й'] = 'q', ['ц'] = 'w', ['у'] = 'e', ['к'] = 'r', ['е'] = 't', ['н'] = 'y', ['г'] = 'u',
['ш'] = 'i', ['щ'] = 'o', ['з'] = 'p', ['ф'] = 'a', ['ы'] = 's', ['в'] = 'd', ['а'] = 'f',
['п'] = 'g', ['р'] = 'h', ['о'] = 'j', ['л'] = 'k', ['д'] = 'l', ['я'] = 'z', ['ч'] = 'x',
['с'] = 'c', ['м'] = 'v', ['и'] = 'b', ['т'] = 'n', ['ь'] = 'm',
['й'] = 'q',
['ц'] = 'w',
['у'] = 'e',
['к'] = 'r',
['е'] = 't',
['н'] = 'y',
['г'] = 'u',
['ш'] = 'i',
['щ'] = 'o',
['з'] = 'p',
['ф'] = 'a',
['ы'] = 's',
['в'] = 'd',
['а'] = 'f',
['п'] = 'g',
['р'] = 'h',
['о'] = 'j',
['л'] = 'k',
['д'] = 'l',
['я'] = 'z',
['ч'] = 'x',
['с'] = 'c',
['м'] = 'v',
['и'] = 'b',
['т'] = 'n',
['ь'] = 'm',
};

private IntPtr Hook(IntPtr hwnd, int msg, IntPtr w, IntPtr l, ref bool handled)
Expand Down
8 changes: 6 additions & 2 deletions src/Dropwheel/UI/OverlayWindow.Back.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ private FrameworkElement MakeBackBubble()
var th = Themes.Current;
var sq = new Border
{
Width = 64, Height = 64, CornerRadius = new CornerRadius(17),
Width = 64,
Height = 64,
CornerRadius = new CornerRadius(17),
Background = new SolidColorBrush(th.TileBg),
BorderBrush = new SolidColorBrush(th.GroupBorder),
BorderThickness = new Thickness(1.2),
Child = new TextBlock
{
Text = "←", FontSize = 24, Foreground = new SolidColorBrush(th.Label),
Text = "←",
FontSize = 24,
Foreground = new SolidColorBrush(th.Label),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
Expand Down
3 changes: 2 additions & 1 deletion src/Dropwheel/UI/OverlayWindow.Bubble.Wire.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ private StackPanel WireBubble(TargetItem t, Border badge, FrameworkElement label
var th = Themes.Current;
var panel = new StackPanel
{
Width = 76, Tag = t,
Width = 76,
Tag = t,
AllowDrop = t.IsGroup || LaunchService.IsFolderTarget(t) || LaunchService.IsRunTarget(t),
Opacity = t.Exists ? 1.0 : 0.4,
Background = Brushes.Transparent
Expand Down
27 changes: 19 additions & 8 deletions src/Dropwheel/UI/OverlayWindow.Bubble.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@ private FrameworkElement MakeBubble(TargetItem t)
UIElement inner = t.IsGroup
? new TextBlock
{
Text = t.Children!.Count.ToString(), FontSize = 20,
FontWeight = FontWeights.Bold, Foreground = new SolidColorBrush(th.Label),
Text = t.Children!.Count.ToString(),
FontSize = 20,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(th.Label),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
: new Image
{
Width = 32, Height = 32, Source = IconService.GetIcon(t.Path),
Width = 32,
Height = 32,
Source = IconService.GetIcon(t.Path),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
var sq = new Border
{
Width = 64, Height = 64, CornerRadius = new CornerRadius(17),
Width = 64,
Height = 64,
CornerRadius = new CornerRadius(17),
Background = new SolidColorBrush(th.TileBg),
BorderBrush = new SolidColorBrush(
t.IsGroup ? th.GroupBorder : t.IsSorter ? th.SorterBorder : th.TileBorder),
Expand All @@ -39,8 +45,10 @@ private FrameworkElement MakeBubble(TargetItem t)
};
var badge = new Border
{
Background = Brushes.MediumSpringGreen, CornerRadius = new CornerRadius(10),
Padding = new Thickness(5, 1, 5, 1), Visibility = Visibility.Collapsed,
Background = Brushes.MediumSpringGreen,
CornerRadius = new CornerRadius(10),
Padding = new Thickness(5, 1, 5, 1),
Visibility = Visibility.Collapsed,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
Child = new TextBlock { Text = "⧉", FontWeight = FontWeights.Bold, FontSize = 12 }
Expand All @@ -60,8 +68,11 @@ private static FrameworkElement MakeLabel(string text)
var color = Luminance(th.Label) < 0.5 ? Color.FromRgb(0xEC, 0xF1, 0xF7) : th.Label;
return new TextBlock
{
Text = text, Foreground = new SolidColorBrush(color), FontSize = 11.5,
TextAlignment = TextAlignment.Center, MaxWidth = 88,
Text = text,
Foreground = new SolidColorBrush(color),
FontSize = 11.5,
TextAlignment = TextAlignment.Center,
MaxWidth = 88,
TextTrimming = TextTrimming.CharacterEllipsis,
Effect = new DropShadowEffect { Color = Colors.Black, BlurRadius = 3, ShadowDepth = 1, Opacity = 0.85 },
};
Expand Down
9 changes: 6 additions & 3 deletions src/Dropwheel/UI/OverlayWindow.Cloud.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ private void BuildCloud()
// MouseLeave and the close timer instantly). Clicking empty space closes.
var backdrop = new System.Windows.Shapes.Ellipse
{
Width = 452, Height = 452,
Width = 452,
Height = 452,
Fill = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)),
};
Canvas.SetLeft(backdrop, HalfSize - 226);
Expand All @@ -69,11 +70,13 @@ private void BuildCloud()
double a = -Math.PI / 2 + i * 2 * Math.PI / n;
var spoke = new Line
{
X1 = HalfSize, Y1 = HalfSize,
X1 = HalfSize,
Y1 = HalfSize,
X2 = HalfSize + (RingR - 52) * Math.Cos(a),
Y2 = HalfSize + (RingR - 52) * Math.Sin(a),
Stroke = new SolidColorBrush(th.Spoke),
StrokeThickness = 2, IsHitTestVisible = false,
StrokeThickness = 2,
IsHitTestVisible = false,
};
Cloud.Children.Add(spoke);
_spokes[items[i]] = spoke;
Expand Down
2 changes: 1 addition & 1 deletion src/Dropwheel/UI/OverlayWindow.Dnd.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public partial class OverlayWindow
private static DropAction Resolve(TargetItem t, DragEventArgs e)
{
if (e.KeyStates.HasFlag(DragDropKeyStates.ControlKey)) return DropAction.Copy;
if (e.KeyStates.HasFlag(DragDropKeyStates.ShiftKey)) return DropAction.Move;
if (e.KeyStates.HasFlag(DragDropKeyStates.ShiftKey)) return DropAction.Move;
if (t.Override != DropAction.Inherit) return t.Override;
return TargetStore.Config.GlobalAction;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Dropwheel/UI/OverlayWindow.Hotkey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private void OnHotkey()
{
var dip = ct.TransformFromDevice.Transform(new Point(c.X, c.Y));
Left = dip.X - HalfSize;
Top = dip.Y - HalfSize;
Top = dip.Y - HalfSize;
}
UpdateOrbScreenPos();
OpenCloud();
Expand Down
2 changes: 1 addition & 1 deletion src/Dropwheel/UI/OverlayWindow.Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private void PlaceWindow()
double l = SystemParameters.VirtualScreenLeft, t = SystemParameters.VirtualScreenTop;
double r = l + SystemParameters.VirtualScreenWidth, b = t + SystemParameters.VirtualScreenHeight;
Left = Math.Clamp(cx - HalfSize, l - HalfSize + 24, r - HalfSize - 24);
Top = Math.Clamp(cy - HalfSize, t - HalfSize + 24, b - HalfSize - 24);
Top = Math.Clamp(cy - HalfSize, t - HalfSize + 24, b - HalfSize - 24);
}

private void OnOrbMouseDown(object sender, MouseButtonEventArgs e)
Expand Down
7 changes: 5 additions & 2 deletions src/Dropwheel/UI/OverlayWindow.Plus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ private FrameworkElement MakePlusTile()
var th = Themes.Current;
var rect = new Rectangle
{
RadiusX = 17, RadiusY = 17,
RadiusX = 17,
RadiusY = 17,
Stroke = new SolidColorBrush(th.TileBorder),
StrokeDashArray = new DoubleCollection { 4, 3 },
StrokeThickness = 1.4,
Fill = Brushes.Transparent
};
var plus = new TextBlock
{
Text = "+", FontSize = 26, Foreground = new SolidColorBrush(th.Label),
Text = "+",
FontSize = 26,
Foreground = new SolidColorBrush(th.Label),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
Expand Down
2 changes: 1 addition & 1 deletion src/Dropwheel/UI/OverlayWindow.Proximity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private void UpdateOrbScreenPos()
var p = Orb.PointToScreen(new Point(23, 23)); // orb center, device px
_orbSX = p.X; _orbSY = p.Y;
double m = ct.TransformToDevice.M11;
_openR2 = 150 * m * 150 * m; // open radius
_openR2 = 150 * m * 150 * m; // open radius
_closeR2 = 340 * m * 340 * m; // close radius
}

Expand Down
54 changes: 39 additions & 15 deletions src/Dropwheel/UI/TargetEditorWindow.Rules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ private FrameworkElement BuildMasterRow(SortRule rule, int index)
};
var summary = new TextBlock
{
Text = MasterSummary(rule), FontSize = 11, Foreground = Palettes.TextMuted,
Text = MasterSummary(rule),
FontSize = 11,
Foreground = Palettes.TextMuted,
TextTrimming = TextTrimming.CharacterEllipsis,
};
var body = new StackPanel { Margin = new Thickness(8, 4, 6, 4) };
Expand All @@ -93,7 +95,9 @@ private FrameworkElement BuildMasterRow(SortRule rule, int index)

var border = new Border
{
Child = body, CornerRadius = new CornerRadius(5), Margin = new Thickness(0, 0, 0, 3),
Child = body,
CornerRadius = new CornerRadius(5),
Margin = new Thickness(0, 0, 0, 3),
Cursor = System.Windows.Input.Cursors.Hand,
Background = sel ? SelectedBg : Brushes.Transparent,
BorderBrush = sel ? SelectedBar : Brushes.Transparent,
Expand Down Expand Up @@ -165,7 +169,9 @@ private void RebuildDetail()

_tokenHint = new TextBlock
{
FontSize = 11, Foreground = Palettes.TextMuted, TextWrapping = TextWrapping.Wrap,
FontSize = 11,
Foreground = Palettes.TextMuted,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 0, 0, 10),
};
DetailHost.Children.Add(_tokenHint);
Expand All @@ -179,8 +185,10 @@ private void RebuildDetail()

var addCond = new Button
{
Content = "+ condition", Padding = new Thickness(6, 1, 6, 1),
HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(0, 4, 0, 0),
Content = "+ condition",
Padding = new Thickness(6, 1, 6, 1),
HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(0, 4, 0, 0),
};
addCond.Click += (_, _) =>
{
Expand All @@ -192,8 +200,10 @@ private void RebuildDetail()

var savePreset = new Button
{
Content = "Save as preset…", Padding = new Thickness(6, 1, 6, 1),
HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(0, 8, 0, 0),
Content = "Save as preset…",
Padding = new Thickness(6, 1, 6, 1),
HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(0, 8, 0, 0),
};
savePreset.Click += (_, _) => OnSaveAsPreset(rule);
DetailHost.Children.Add(savePreset);
Expand All @@ -216,7 +226,9 @@ private FrameworkElement BuildConditionRow(SortRule rule, RuleCondition cond)
cond.Op = ops[0];
var opWord = new TextBlock
{
Text = OpWord(cond.Op), Foreground = Palettes.TextMuted, VerticalAlignment = VerticalAlignment.Center,
Text = OpWord(cond.Op),
Foreground = Palettes.TextMuted,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(8, 0, 4, 0),
};
DockPanel.SetDock(opWord, Dock.Right);
Expand Down Expand Up @@ -247,8 +259,11 @@ private FrameworkElement BuildConditionRow(SortRule rule, RuleCondition cond)
var value = new TextBox { Text = cond.Value };
var hint = new TextBlock
{
Text = WatermarkFor(cond.Field), Foreground = Palettes.TextMuted, IsHitTestVisible = false,
Margin = new Thickness(6, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center,
Text = WatermarkFor(cond.Field),
Foreground = Palettes.TextMuted,
IsHitTestVisible = false,
Margin = new Thickness(6, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
Visibility = string.IsNullOrEmpty(cond.Value) ? Visibility.Visible : Visibility.Collapsed,
};
value.TextChanged += (_, _) =>
Expand Down Expand Up @@ -364,7 +379,9 @@ private void RefreshMatches()
var here = files.Where(f => SortService.MatchedRuleIndex(_rules, f) == _selected).ToArray();
_matchesHost.Children.Add(new TextBlock
{
Text = $"Routes here: {here.Length} of {files.Length}", FontSize = 11, Foreground = Palettes.TextMuted,
Text = $"Routes here: {here.Length} of {files.Length}",
FontSize = 11,
Foreground = Palettes.TextMuted,
Margin = new Thickness(0, 0, 0, 2),
});
bool showDest = _rules[_selected].Dest.Contains("${", StringComparison.Ordinal);
Expand All @@ -374,7 +391,8 @@ private void RefreshMatches()
Text = showDest
? $"{Path.GetFileName(f)} → {ResolvedDestLabel(_rules[_selected], Path.GetFileName(f))}"
: Path.GetFileName(f),
FontSize = 11, TextTrimming = TextTrimming.CharacterEllipsis,
FontSize = 11,
TextTrimming = TextTrimming.CharacterEllipsis,
});
// Size/age conditions are treated as zero in the preview: the test input is names without
// real files on disk. Warn if any rule has such a condition — a file might have been silently
Expand Down Expand Up @@ -499,8 +517,11 @@ private Button MoveButton(string glyph, bool enabled, Action onClick)
{
var b = new Button
{
Content = glyph, Width = 24, Margin = new Thickness(2, 0, 0, 0),
Padding = new Thickness(0), IsEnabled = enabled,
Content = glyph,
Width = 24,
Margin = new Thickness(2, 0, 0, 0),
Padding = new Thickness(0),
IsEnabled = enabled,
};
b.Click += (_, _) => onClick();
return b;
Expand Down Expand Up @@ -548,7 +569,10 @@ private bool TryValidateRules(out string error)

private static TextBlock Hint(string text) => new()
{
Text = text, FontSize = 11, Foreground = Palettes.TextMuted, TextWrapping = TextWrapping.Wrap,
Text = text,
FontSize = 11,
Foreground = Palettes.TextMuted,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 2, 0, 2),
};

Expand Down