Compare commits
10
Commits
1992f2f963
..
v1.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42e93346ab | ||
|
|
9ab26afc16 | ||
|
|
743974a0c1 | ||
|
|
9c6ec489e1 | ||
|
|
b2c7b31eb5 | ||
|
|
5670ad0e64 | ||
|
|
1540be3b79 | ||
|
|
88dd931cb4 | ||
|
|
70b83a5fec | ||
|
|
0682f2b875 |
@@ -138,9 +138,22 @@ jobs:
|
||||
- name: Build the installer
|
||||
run: ./Packaging/build-installer.ps1 -Version ${{ steps.version.outputs.plain }}
|
||||
|
||||
# The artifact is where the package waits to be uploaded to Partner Center
|
||||
# The artifact is where the package waits to be uploaded to Partner Center.
|
||||
#
|
||||
# The action comes from this Gitea rather than from github.com, and it is
|
||||
# a fork of actions/upload-artifact with one line changed. Upstream reads
|
||||
# GITHUB_SERVER_URL, takes every host but github.com and its enterprise
|
||||
# ones for a GitHub Enterprise Server — where the v4 artifact backend does
|
||||
# not exist — and throws before it makes a request. Here that backend does
|
||||
# exist and answers, but the check runs on the runner ahead of the network
|
||||
# and never lets it prove itself; nor can the name be corrected from this
|
||||
# file, as the runner sets the GITHUB_ ones over the env of a step. The
|
||||
# fork carries the reasoning in full in its GITEA-PATCH.md.
|
||||
#
|
||||
# The tag is a fixed one. A floating tag is how this broke before: the
|
||||
# action is fetched at run time, and what arrives can change on its own
|
||||
- name: Keep the package
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: https://git.alrakis.kz/actions/upload-artifact@v4-gitea
|
||||
with:
|
||||
name: msix-${{ steps.version.outputs.version }}
|
||||
path: artifacts/packages/
|
||||
@@ -149,7 +162,7 @@ jobs:
|
||||
# The .wixpdb next to each installer is left out on purpose: it is of use
|
||||
# only when something has to be traced back to the WiX source
|
||||
- name: Keep the installer
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: https://git.alrakis.kz/actions/upload-artifact@v4-gitea
|
||||
with:
|
||||
name: installer-${{ steps.version.outputs.plain }}
|
||||
path: artifacts/installers/*.msi
|
||||
@@ -167,10 +180,8 @@ jobs:
|
||||
|
||||
# The GITHUB_ names are what Gitea itself hands to the workflow — its
|
||||
# actions repeat those of GitHub, and the addresses in them point at
|
||||
# this Gitea instance. GITHUB_API_URL used not to reach the steps at
|
||||
# all, so the address is put together from the server one when empty
|
||||
$root = if ($env:GITHUB_API_URL) { $env:GITHUB_API_URL } else { "$env:GITHUB_SERVER_URL/api/v1" }
|
||||
$api = "$root/repos/$env:GITHUB_REPOSITORY/releases"
|
||||
# this Gitea instance
|
||||
$api = "$env:GITHUB_API_URL/repos/$env:GITHUB_REPOSITORY/releases"
|
||||
$headers = @{ Authorization = "token $env:GITEA_TOKEN" }
|
||||
|
||||
# Gitea makes a release of its own for a pushed tag, so the release is
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' != ''">true</PublishReadyToRun>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<Product>CursorLang</Product>
|
||||
<Company>Aleksandr Neichev</Company>
|
||||
<Description>Shows the keyboard layout at the cursor</Description>
|
||||
|
||||
@@ -68,42 +68,42 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
return;
|
||||
}
|
||||
|
||||
bool atFixedPoint = _settings.PlacementMode == PopupPlacementMode.FixedPoint;
|
||||
PopupWindowNative.Rect? anchor = TryGetAnchor();
|
||||
PopupModeSettings mode = ModeFor(anchor);
|
||||
|
||||
PopupWindowNative.Rect work = default;
|
||||
PopupWindowNative.Rect anchor = default;
|
||||
double scale;
|
||||
|
||||
if (atFixedPoint)
|
||||
if (anchor is { } at)
|
||||
{
|
||||
(work, scale) = PopupWindowNative.GetActiveMonitorWorkArea();
|
||||
scale = PopupWindowNative.GetScaleAt(new PopupWindowNative.Point { X = at.Left, Y = at.Top });
|
||||
}
|
||||
else
|
||||
{
|
||||
anchor = GetAnchor();
|
||||
scale = PopupWindowNative.GetScaleAt(new PopupWindowNative.Point { X = anchor.Left, Y = anchor.Top });
|
||||
(work, scale) = PopupWindowNative.GetActiveMonitorWorkArea();
|
||||
}
|
||||
|
||||
EnsureFont(scale);
|
||||
EnsureFont(mode, scale);
|
||||
|
||||
Size measured = MeasureText(text);
|
||||
int width = measured.Width + (2 * PopupLayout.ToPixels(PaddingX, scale));
|
||||
int height = measured.Height + (2 * PopupLayout.ToPixels(PaddingY, scale));
|
||||
|
||||
PopupWindowNative.Point position = atFixedPoint
|
||||
? PopupLayout.OnScreen(
|
||||
work,
|
||||
_settings.FixedPoint.Position,
|
||||
PopupLayout.ToPixels(_settings.FixedPoint.Offset, scale),
|
||||
PopupWindowNative.Point position = anchor is { } near
|
||||
? PopupLayout.NearAnchor(
|
||||
near,
|
||||
SideForMode(),
|
||||
PopupLayout.ToPixels(mode.Offset, scale),
|
||||
width,
|
||||
height)
|
||||
: PopupLayout.NearAnchor(
|
||||
anchor,
|
||||
SideForMode(),
|
||||
PopupLayout.ToPixels(_settings.Current.Offset, scale),
|
||||
: PopupLayout.OnScreen(
|
||||
work,
|
||||
_settings.FixedPoint.Position,
|
||||
PopupLayout.ToPixels(mode.Offset, scale),
|
||||
width,
|
||||
height);
|
||||
|
||||
if (!Draw(text, position, width, height, PopupLayout.ToPixels(CornerRadius, scale)))
|
||||
if (!Draw(mode, text, position, width, height, PopupLayout.ToPixels(CornerRadius, scale)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -139,7 +139,8 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
/// kilobytes for the length of one call, the popup is shown rarely, and a cached one
|
||||
/// would have to be rebuilt on every change of size, colour or scale anyway.
|
||||
/// </remarks>
|
||||
private bool Draw(string text, PopupWindowNative.Point at, int width, int height, int radius)
|
||||
private bool Draw(
|
||||
PopupModeSettings mode, string text, PopupWindowNative.Point at, int width, int height, int radius)
|
||||
{
|
||||
IntPtr screen = GdiNative.GetDC(IntPtr.Zero);
|
||||
if (screen == IntPtr.Zero)
|
||||
@@ -166,8 +167,8 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
|
||||
GdiNative.SelectObject(memory, surface);
|
||||
|
||||
Fill(bits, width, height);
|
||||
DrawText(memory, text, width, height);
|
||||
Fill(mode, bits, width, height);
|
||||
DrawText(mode, memory, text, width, height);
|
||||
|
||||
// GDI writes nothing into the alpha channel, so the letters it just drew are
|
||||
// sitting at zero alpha and would come out invisible. The inside of the
|
||||
@@ -178,7 +179,7 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
RoundTheCorners(bits, width, height, radius);
|
||||
|
||||
var size = new WindowNative.Size { Width = width, Height = height };
|
||||
var alpha = (byte)Math.Clamp(Math.Round(_settings.Current.Opacity * 255), 0, 255);
|
||||
var alpha = (byte)Math.Clamp(Math.Round(mode.Opacity * 255), 0, 255);
|
||||
|
||||
return WindowNative.SetContent(Handle, at, size, memory, alpha);
|
||||
}
|
||||
@@ -200,9 +201,9 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void Fill(IntPtr bits, int width, int height)
|
||||
private static void Fill(PopupModeSettings mode, IntPtr bits, int width, int height)
|
||||
{
|
||||
Color background = _settings.Current.BackgroundColor;
|
||||
Color background = mode.BackgroundColor;
|
||||
|
||||
// Straight into the bitmap rather than through a brush: the pixels have to be
|
||||
// written anyway to carry an alpha channel GDI would not touch
|
||||
@@ -216,7 +217,8 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawText(IntPtr deviceContext, string text, int width, int height)
|
||||
private void DrawText(
|
||||
PopupModeSettings mode, IntPtr deviceContext, string text, int width, int height)
|
||||
{
|
||||
if (_font == IntPtr.Zero || text.Length == 0)
|
||||
{
|
||||
@@ -227,7 +229,7 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
|
||||
IntPtr previousFont = GdiNative.SelectObject(deviceContext, _font);
|
||||
GdiNative.SetBkMode(deviceContext, GdiNative.TRANSPARENT);
|
||||
GdiNative.SetTextColor(deviceContext, GdiNative.ToColorRef(_settings.Current.ForegroundColor));
|
||||
GdiNative.SetTextColor(deviceContext, GdiNative.ToColorRef(mode.ForegroundColor));
|
||||
|
||||
GdiNative.DrawText(deviceContext, text, text.Length, ref bounds,
|
||||
GdiNative.DT_SINGLELINE | GdiNative.DT_CENTER | GdiNative.DT_VCENTER |
|
||||
@@ -329,18 +331,40 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
return (int)((alpha << 24) | (red << 16) | (green << 8) | blue);
|
||||
}
|
||||
|
||||
// The anchor point: the caret in the input field or the mouse cursor. The cursor
|
||||
// is a rectangle of zero size, so the corner computation is shared by both
|
||||
private PopupWindowNative.Rect GetAnchor()
|
||||
/// <summary>
|
||||
/// What the popup is placed next to, or <c>null</c> when there is nothing: the fixed
|
||||
/// point mode, and the caret mode where the application reports no caret.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The cursor is a rectangle of zero size, so the corner arithmetic is shared by it
|
||||
/// and the caret.
|
||||
/// </remarks>
|
||||
private PopupWindowNative.Rect? TryGetAnchor() => _settings.PlacementMode switch
|
||||
{
|
||||
if (_settings.PlacementMode == PopupPlacementMode.AtCaret &&
|
||||
CaretNative.TryGetCaretRect() is { } caret)
|
||||
{
|
||||
return caret;
|
||||
}
|
||||
PopupPlacementMode.FixedPoint => null,
|
||||
PopupPlacementMode.AtCaret => CaretNative.TryGetCaretRect(),
|
||||
_ => PopupLayout.AsAnchor(PopupWindowNative.GetCursorPosition()),
|
||||
};
|
||||
|
||||
return PopupLayout.AsAnchor(PopupWindowNative.GetCursorPosition());
|
||||
}
|
||||
/// <summary>
|
||||
/// The settings the popup is shown with: those of the mode chosen, or those of the
|
||||
/// fixed point when there is no anchor to stand next to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The caret mode falls back to the fixed point rather than to the mouse cursor:
|
||||
/// the cursor is wherever it was last left — off to a side, on another monitor, or
|
||||
/// over the very text being typed — and a popup that lands there while the eyes are
|
||||
/// on the caret is one that is looked for and not found. The fixed point is always
|
||||
/// in the same place, so it is known where to look.
|
||||
///
|
||||
/// The look comes from the fixed point mode too, not just the place. The two are set
|
||||
/// up together for a reason: the popup by the caret is small and quiet because it
|
||||
/// sits inside a text being read, while the one in the corner of the monitor is
|
||||
/// looked for on purpose and is set larger. Keeping the caret look at the corner
|
||||
/// would put a popup meant to go unnoticed where nothing else draws the eye.
|
||||
/// </remarks>
|
||||
private PopupModeSettings ModeFor(PopupWindowNative.Rect? anchor) =>
|
||||
anchor is null ? _settings.FixedPoint : _settings.Current;
|
||||
|
||||
// The caret has two sides to choose from and the cursor has six, so each mode names
|
||||
// its own side in its own terms
|
||||
@@ -373,10 +397,10 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
// The font is rebuilt only when the size in the settings or the monitor scale
|
||||
// changes: it is the one expensive thing a show does. A switch of the placement
|
||||
// mode counts as a change of the size, since the size belongs to the mode
|
||||
private void EnsureFont(double scale)
|
||||
private void EnsureFont(PopupModeSettings mode, double scale)
|
||||
{
|
||||
if (_font != IntPtr.Zero &&
|
||||
Math.Abs(_fontSize - _settings.Current.FontSize) < 0.01 &&
|
||||
Math.Abs(_fontSize - mode.FontSize) < 0.01 &&
|
||||
Math.Abs(_fontScale - scale) < 0.01)
|
||||
{
|
||||
return;
|
||||
@@ -384,7 +408,7 @@ internal sealed class NativePopupWindow : NativeWindow, ILayoutPopupWindow
|
||||
|
||||
ReleaseFont();
|
||||
|
||||
_fontSize = _settings.Current.FontSize;
|
||||
_fontSize = mode.FontSize;
|
||||
_fontScale = scale;
|
||||
_font = GdiNative.CreateFont(_fontSize, scale);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ internal static class CaretNative
|
||||
private static PopupWindowNative.Rect? TryGetAutomationCaret()
|
||||
{
|
||||
// A hung application must not hang the popup along with it: we wait for the
|
||||
// answer for a limited time, otherwise we show the popup at the cursor
|
||||
// answer for a limited time, otherwise the popup goes to the fixed point
|
||||
Task<PopupWindowNative.Rect?> query = Task.Run(QueryAutomationCaret);
|
||||
return query.Wait(AutomationTimeout) ? query.Result : null;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ internal static class CaretNative
|
||||
}
|
||||
#else
|
||||
// Built without UI Automation: Chromium and Electron keep the system caret and MSAA
|
||||
// steps above, and where those stay silent the popup falls back to the cursor
|
||||
// steps above, and where those stay silent the popup falls back to the fixed point
|
||||
private static PopupWindowNative.Rect? TryGetAutomationCaret() => null;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ public enum PopupPlacementMode
|
||||
|
||||
/// <summary>
|
||||
/// Next to the caret in the active input field. When the application does not
|
||||
/// report its position, the popup is shown at the mouse cursor.
|
||||
/// report its position, the popup is shown as in <see cref="FixedPoint"/> — at the
|
||||
/// place and with the look that mode is set up with.
|
||||
/// </summary>
|
||||
AtCaret,
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using CursorLang.Core.Models;
|
||||
using CursorLang.Core.Services;
|
||||
using CursorLang.Settings.Tests.Infrastructure;
|
||||
using CursorLang.Settings.ViewModels;
|
||||
using CursorLang.Tests.Shared;
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' != ''">true</PublishReadyToRun>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<Product>CursorLang</Product>
|
||||
<Company>Aleksandr Neichev</Company>
|
||||
<Description>Settings window of CursorLang</Description>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}"
|
||||
Title="{Binding Title}"
|
||||
Width="1000" SizeToContent="Height" MaxHeight="900"
|
||||
Width="560" SizeToContent="Height" MaxHeight="940"
|
||||
ResizeMode="CanMinimize"
|
||||
Background="{DynamicResource Theme.WindowBackground}"
|
||||
Foreground="{DynamicResource Theme.Foreground}">
|
||||
@@ -59,19 +59,7 @@
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<!-- The settings are laid out in two columns: this way the window fits on the
|
||||
screen entirely and does without scrolling. Scrolling is kept for the case of
|
||||
a large system font, with which the content is taller than the monitor after all.
|
||||
|
||||
The left column holds what the popup does; the right one holds the single
|
||||
section describing how and where the popup looks -->
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="16">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel>
|
||||
|
||||
<GroupBox Header="{Binding Localization[SectionInterface]}">
|
||||
@@ -104,105 +92,6 @@
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="{Binding Localization[SectionBehavior]}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="164" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Style="{StaticResource FieldLabel}"
|
||||
Text="{Binding Localization[DurationLabel]}" />
|
||||
<Slider Grid.Column="1" Minimum="200" Maximum="5000" TickFrequency="100"
|
||||
Value="{Binding Settings.DurationMilliseconds}" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource FieldValue}"
|
||||
Text="{Binding Settings.DurationMilliseconds, StringFormat={}{0:F0}}" />
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}"
|
||||
Text="{Binding Localization[MillisecondsSuffix]}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1" Margin="0,12,12,0"
|
||||
Style="{StaticResource FieldLabel}"
|
||||
Text="{Binding Localization[CapsLockLabel]}" />
|
||||
<CheckBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0"
|
||||
IsChecked="{Binding Settings.UseCapsLockHotkey}"
|
||||
Content="{Binding Localization[CapsLockHotkeyCheck]}" />
|
||||
|
||||
<TextBlock Grid.Row="2" Margin="0,12,12,0"
|
||||
Style="{StaticResource FieldLabel}"
|
||||
Text="{Binding Localization[CapsLockHoldLabel]}" />
|
||||
<Slider Grid.Row="2" Grid.Column="1" Margin="0,12,0,0"
|
||||
Minimum="150" Maximum="1500" TickFrequency="50"
|
||||
Value="{Binding Settings.CapsLockHoldMilliseconds}"
|
||||
IsEnabled="{Binding Settings.UseCapsLockHotkey}" />
|
||||
<StackPanel Grid.Row="2" Grid.Column="2" Margin="0,12,0,0"
|
||||
Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource FieldValue}"
|
||||
Text="{Binding Settings.CapsLockHoldMilliseconds, StringFormat={}{0:F0}}" />
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}"
|
||||
Text="{Binding Localization[MillisecondsSuffix]}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="0,6,0,0" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}">
|
||||
<Run Text="{Binding Localization[CapsLockHoldHint], Mode=OneWay}" />
|
||||
<InlineUIContainer BaselineAlignment="Baseline">
|
||||
<!-- The elevation caveat lives in the tooltip to keep the section compact -->
|
||||
<TextBlock Text="{Binding Localization[MoreInfoLink]}"
|
||||
Foreground="{DynamicResource Theme.Accent}"
|
||||
TextDecorations="Underline"
|
||||
Cursor="Help"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTipService.ShowDuration="60000"
|
||||
Visibility="{Binding Settings.UseCapsLockHotkey, Converter={StaticResource BooleanToVisibility}}">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip>
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Text="{Binding Localization[CapsLockElevationHint]}" />
|
||||
</ToolTip>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</InlineUIContainer>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="4" Margin="0,12,12,0"
|
||||
Style="{StaticResource FieldLabel}"
|
||||
Visibility="{Binding IsStartupAvailable, Converter={StaticResource BooleanToVisibility}}"
|
||||
Text="{Binding Localization[StartupLabel]}" />
|
||||
<CheckBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0"
|
||||
Visibility="{Binding IsStartupAvailable, Converter={StaticResource BooleanToVisibility}}"
|
||||
IsEnabled="{Binding CanChangeStartup}"
|
||||
IsChecked="{Binding RunAtStartup}"
|
||||
Content="{Binding Localization[StartupCheck]}" />
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="0,6,0,0" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}"
|
||||
Visibility="{Binding IsStartupLocked, Converter={StaticResource BooleanToVisibility}}"
|
||||
Text="{Binding Localization[StartupLockedHint]}" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="16,0,0,0">
|
||||
|
||||
<!-- Where the popup appears and how it looks are one question for the
|
||||
user, so both live in a single section: the rule is picked at the
|
||||
top and its result is seen in the preview at the bottom -->
|
||||
<GroupBox Header="{Binding Localization[SectionPopup]}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -358,8 +247,6 @@
|
||||
Converter={StaticResource EnumToVisibility},
|
||||
ConverterParameter=FixedPoint}" />
|
||||
|
||||
<!-- A hairline between the two halves of the section: the
|
||||
placement above, the look of the popup below -->
|
||||
<Border Grid.Row="7" Grid.ColumnSpan="3"
|
||||
Margin="0,16,0,0" Height="1"
|
||||
Background="{DynamicResource Theme.ControlBorder}" />
|
||||
@@ -422,7 +309,98 @@
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="{Binding Localization[SectionBehavior]}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="164" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Style="{StaticResource FieldLabel}"
|
||||
Text="{Binding Localization[DurationLabel]}" />
|
||||
<Slider Grid.Column="1" Minimum="200" Maximum="5000" TickFrequency="100"
|
||||
Value="{Binding Settings.DurationMilliseconds}" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource FieldValue}"
|
||||
Text="{Binding Settings.DurationMilliseconds, StringFormat={}{0:F0}}" />
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}"
|
||||
Text="{Binding Localization[MillisecondsSuffix]}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1" Margin="0,12,12,0"
|
||||
Style="{StaticResource FieldLabel}"
|
||||
Text="{Binding Localization[CapsLockLabel]}" />
|
||||
<CheckBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0"
|
||||
IsChecked="{Binding Settings.UseCapsLockHotkey}"
|
||||
Content="{Binding Localization[CapsLockHotkeyCheck]}" />
|
||||
|
||||
<TextBlock Grid.Row="2" Margin="0,12,12,0"
|
||||
Style="{StaticResource FieldLabel}"
|
||||
Text="{Binding Localization[CapsLockHoldLabel]}" />
|
||||
<Slider Grid.Row="2" Grid.Column="1" Margin="0,12,0,0"
|
||||
Minimum="150" Maximum="1500" TickFrequency="50"
|
||||
Value="{Binding Settings.CapsLockHoldMilliseconds}"
|
||||
IsEnabled="{Binding Settings.UseCapsLockHotkey}" />
|
||||
<StackPanel Grid.Row="2" Grid.Column="2" Margin="0,12,0,0"
|
||||
Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource FieldValue}"
|
||||
Text="{Binding Settings.CapsLockHoldMilliseconds, StringFormat={}{0:F0}}" />
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}"
|
||||
Text="{Binding Localization[MillisecondsSuffix]}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="0,6,0,0" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}">
|
||||
<Run Text="{Binding Localization[CapsLockHoldHint], Mode=OneWay}" />
|
||||
<InlineUIContainer BaselineAlignment="Baseline">
|
||||
<!-- The elevation caveat lives in the tooltip to keep the section compact -->
|
||||
<TextBlock Text="{Binding Localization[MoreInfoLink]}"
|
||||
Foreground="{DynamicResource Theme.Accent}"
|
||||
TextDecorations="Underline"
|
||||
Cursor="Help"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTipService.ShowDuration="60000"
|
||||
Visibility="{Binding Settings.UseCapsLockHotkey, Converter={StaticResource BooleanToVisibility}}">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip>
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Text="{Binding Localization[CapsLockElevationHint]}" />
|
||||
</ToolTip>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</InlineUIContainer>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="4" Margin="0,12,12,0"
|
||||
Style="{StaticResource FieldLabel}"
|
||||
Visibility="{Binding IsStartupAvailable, Converter={StaticResource BooleanToVisibility}}"
|
||||
Text="{Binding Localization[StartupLabel]}" />
|
||||
<CheckBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0"
|
||||
Visibility="{Binding IsStartupAvailable, Converter={StaticResource BooleanToVisibility}}"
|
||||
IsEnabled="{Binding CanChangeStartup}"
|
||||
IsChecked="{Binding RunAtStartup}"
|
||||
Content="{Binding Localization[StartupCheck]}" />
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="0,6,0,0" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Theme.SecondaryForeground}"
|
||||
Visibility="{Binding IsStartupLocked, Converter={StaticResource BooleanToVisibility}}"
|
||||
Text="{Binding Localization[StartupLockedHint]}" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Window>
|
||||
|
||||
@@ -136,8 +136,8 @@
|
||||
Condition="REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE" />
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!-- Offered at the end of the wizard, the way an installer usually does -->
|
||||
<Property Id="WixShellExecTarget" Value="[#CursorLang.exe]" />
|
||||
<SetProperty Id="WixShellExecTarget" Value="[INSTALLFOLDER]CursorLang.exe"
|
||||
After="CostFinalize" Sequence="ui" />
|
||||
<CustomAction Id="LaunchApplication" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
|
||||
DllEntry="WixShellExec" Impersonate="yes" Return="ignore" />
|
||||
|
||||
|
||||
@@ -169,6 +169,24 @@ MSIX всегда выполняются в контексте вошедшег
|
||||
на месте, а не исчезает из раздела — появляющаяся и исчезающая строка сдвигала бы
|
||||
всё, что ниже, при каждой смене места.
|
||||
|
||||
Положение каретки сообщает не всякое приложение, и тогда рядом с курсором ввода —
|
||||
нигде. Подсказка уходит на фиксированную точку: место, отступ и оформление берутся
|
||||
оттуда, из режима каретки не берётся ничего. Курсор мыши был бы запасным вариантом
|
||||
поближе — и неверным: он там, где его оставили, сбоку или на другом мониторе, тогда
|
||||
как смотрят на каретку. Фиксированная точка всегда на одном и том же месте, и потому
|
||||
известно, куда смотреть, — а вместе с местом должно приходить и оформление: подсказка,
|
||||
настроенная мелкой и незаметной для середины текста, в углу монитора останется
|
||||
незамеченной.
|
||||
|
||||
Какие-то приложения, решает не то, на чём они написаны, а то, что окно о себе
|
||||
сообщает. Обычный элемент Win32 заводит системную каретку, и её читают сразу;
|
||||
Chrome и Electron рисуют свою и сообщают о ней через интерфейсы доступности.
|
||||
Окну, которое рисует текст на голом холсте, сообщать нечего — и оба случая
|
||||
уживаются в одном приложении: редакторы JetBrains отвечают, потому что каретку
|
||||
для них держит их среда выполнения, а панель инструментов, нарисованная Compose,
|
||||
не содержит текстового элемента, который нашли бы UI Automation или Java Access
|
||||
Bridge. Ради такого окна фиксированная точка и нужна.
|
||||
|
||||
К какому режиму относится оформление, объясняет всплывающая подсказка рядом с самим
|
||||
режимом, а не строка текста в разделе: ползунки ниже показывают другие числа после
|
||||
переключения режима, и это вопрос, который задают один раз.
|
||||
|
||||
@@ -163,6 +163,24 @@ zero and the settings window shows it greyed out. It stays in place rather than
|
||||
leaving the section — a row that comes and goes would move everything below it on
|
||||
every switch of the place.
|
||||
|
||||
Not every application reports where its caret is, and next to the caret is then
|
||||
nowhere. The popup goes to the fixed point instead — the place, the offset and the
|
||||
look all from that mode, none of them from the caret one. The mouse cursor would be
|
||||
the nearer fallback and is the wrong one: it is wherever it was last left, off to a
|
||||
side or on another monitor, while the eyes are on the caret. The fixed point is
|
||||
always in the same place, so it is known where to look — and the look must come with
|
||||
it, since a popup set small and quiet for the middle of a text goes unnoticed in the
|
||||
corner of a monitor.
|
||||
|
||||
Some applications those are is decided by what a window exposes rather than by
|
||||
what it is written in. A Win32 control creates a system caret and is read at once;
|
||||
Chromium and Electron draw their own and report it over the accessibility
|
||||
interfaces. A window that paints its text into a bare canvas has nothing to report
|
||||
at all — and both kinds live in the same application: the editors of a JetBrains
|
||||
IDE answer, since the runtime keeps a caret for them, while a tool window whose
|
||||
interface is painted by Compose holds no text component for UI Automation or the
|
||||
Java Access Bridge to find. That window is where the fixed point earns its keep.
|
||||
|
||||
Which mode the look belongs to is explained in a tooltip next to the mode itself
|
||||
rather than by a line of text in the section: the sliders below show other numbers
|
||||
after a switch of the mode, and that is a question asked once.
|
||||
|
||||
Reference in New Issue
Block a user