6 Commits
Author SHA1 Message Date
alex 246751a4fd Merge pull request 'Fix certification comment' (#15) from fix-certification-comment into master
Release / release (push) Failing after 7m3s
Reviewed-on: #15
2026-09-03 05:00:16 +00:00
alex 53720e95db fixed width resize when change screen scale
Pull request / build (pull_request) Successful in 37s
2026-09-03 09:55:29 +05:00
alex ccd914d6fe fix resize on different screens try -1 2026-09-03 09:40:58 +05:00
alex cd63644a83 added material for certification 2026-09-03 08:18:05 +05:00
alex 1bad7785d0 add Store listing images: 9:16 poster and 300x300 logo
Partner Center's screenshots and logos steps needed sizes the MSIX package
doesn't produce, so they are drawn separately with the same mark.
2026-09-01 11:01:45 +05:00
alex 0ba581946f add site address to og:url, og:image and canonical link
The site is now live at cursor-lang.alrakis.kz, so these tags can be filled in.
2026-09-01 10:19:07 +05:00
18 changed files with 238 additions and 5 deletions
@@ -1,5 +1,7 @@
using System.Windows; using System.Windows;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using CursorLang.Core.Interop; using CursorLang.Core.Interop;
using CursorLang.Settings.Interop; using CursorLang.Settings.Interop;
@@ -23,6 +25,15 @@ public sealed class MainWindowPlacement
// what has to be remembered is only what the user chose // what has to be remembered is only what the user chose
private bool _isPlacing; private bool _isPlacing;
// Captured once, before anything narrows MaxHeight to a particular monitor —
// otherwise a later, more generous monitor would stay capped at whatever a
// previous, shorter one left behind
private double? _designMaxHeight;
// Captured once, so a DPI change has a known-correct value to reassert — see
// RestoreDesignWidth
private double? _designWidth;
/// <summary> /// <summary>
/// Takes over the placement of the window: puts it in place by the first show /// Takes over the placement of the window: puts it in place by the first show
/// and follows where the user moves it. /// and follows where the user moves it.
@@ -31,6 +42,7 @@ public sealed class MainWindowPlacement
{ {
window.SourceInitialized += OnSourceInitialized; window.SourceInitialized += OnSourceInitialized;
window.LocationChanged += OnLocationChanged; window.LocationChanged += OnLocationChanged;
window.DpiChanged += OnDpiChanged;
} }
/// <summary> /// <summary>
@@ -83,10 +95,108 @@ public sealed class MainWindowPlacement
} }
window.SourceInitialized -= OnSourceInitialized; window.SourceInitialized -= OnSourceInitialized;
_designMaxHeight = window.MaxHeight;
_designWidth = window.Width;
LimitHeightToOwnMonitor(window);
window.UpdateLayout(); window.UpdateLayout();
Apply(window); Apply(window);
} }
// A monitor is not necessarily final at creation time — Windows may place the new
// window on one monitor before Apply moves it to another, and the user is free to
// drag it to a third one later. Every one of those is a real DPI change, and this
// runs again for each: recomputing the cap from whichever monitor holds the window
// right now, rather than trusting a value worked out for a previous one.
private void OnDpiChanged(object sender, DpiChangedEventArgs e)
{
if (sender is not Window window)
{
return;
}
// Deferred rather than run inline: this event fires while WPF is still in
// the middle of its own response to the same DPI change (its per-monitor
// rescale of Width touches it after this handler if the fix-up runs
// synchronously, undoing it). Posting behind that on the dispatcher queue
// lets our fix-up run once WPF's own pass has finished.
window.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
LimitHeightToOwnMonitor(window);
RestoreDesignWidth(window);
ReapplySizeToContent(window);
}));
}
// The width is a plain, explicit value rather than something SizeToContent
// computes, and WPF's own per-monitor rescaling does not reliably keep it at the
// same logical width when the system's scaling changes live under an
// already-open window, as opposed to the window being dragged onto a different
// monitor — it can come out scaled by roughly the ratio between the old and the
// new DPI instead of staying put. Reasserting the original value here is simpler
// than chasing exactly where that rescale goes wrong.
private void RestoreDesignWidth(Window window)
{
if (_designWidth is { } designWidth)
{
window.Width = designWidth;
}
}
// UpdateLayout alone settles the measure/arrange pass of the visual tree, but
// does not reliably make WPF redo its own step of resizing the native window to
// match SizeToContent when the change originates from a DPI event rather than an
// ordinary content change. Left alone, the window can end up either too tall — a
// blank strip below the real content, once MaxHeight has just pulled the content
// shorter — or stuck too short after being dragged back to a monitor with room to
// grow again. Turning SizeToContent off and back on forces that resizing step to
// run again from scratch.
private static void ReapplySizeToContent(Window window)
{
SizeToContent original = window.SizeToContent;
window.SizeToContent = SizeToContent.Manual;
window.SizeToContent = original;
window.UpdateLayout();
}
// MaxHeight in XAML is a constant tuned for an ordinary desktop monitor at 100%
// scaling. At a high scale factor the same number of device-independent pixels
// turns into more physical pixels than a short or heavily scaled monitor has, and
// SizeToContent then grows the window past the bottom of the screen instead of
// asking the ScrollViewer inside it to scroll — with nothing to grab, the excess
// is unreachable. Capping MaxHeight to what the window's own monitor really offers
// keeps the whole window on screen and lets the ScrollViewer take over.
private void LimitHeightToOwnMonitor(Window window)
{
if (_designMaxHeight is not { } designMaxHeight)
{
return;
}
IntPtr handle = new WindowInteropHelper(window).Handle;
if (handle == IntPtr.Zero
|| WindowPlacementNative.TryGetBounds(handle) is not { } bounds
|| WindowPlacementNative.TryGetWorkAreaNear(bounds) is not { } work
|| IsEmpty(work))
{
return;
}
double scale = VisualTreeHelper.GetDpi(window).DpiScaleY;
if (scale <= 0)
{
return;
}
// Leaves room for the title bar and a margin from the edges of the screen,
// in the same units as the work area height once the monitor's scale is
// divided out
const double reservedForChrome = 80;
double workAreaHeight = (work.Bottom - work.Top) / scale;
window.MaxHeight = Math.Min(designMaxHeight, Math.Max(200, workAreaHeight - reservedForChrome));
}
private void OnLocationChanged(object? sender, EventArgs e) private void OnLocationChanged(object? sender, EventArgs e)
{ {
if (_isPlacing || sender is not Window { WindowState: WindowState.Normal } window) if (_isPlacing || sender is not Window { WindowState: WindowState.Normal } window)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+84
View File
@@ -0,0 +1,84 @@
<#
.SYNOPSIS
Draws the Store 9:16 poster (720x1080), matching the mark drawn by
Packaging\New-Assets.ps1 (a dark plate with a white "Aя").
.DESCRIPTION
One-off script, not part of the packaging pipeline: this poster is a Store
listing image, uploaded by hand in Partner Center rather than shipped
inside the MSIX, so it does not belong next to the logos that build feeds
into the package.
#>
[CmdletBinding()]
param(
[string] $OutPath = (Join-Path $PSScriptRoot 'Poster720x1080.png')
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$Width = 720
$Height = 1080
$Background = [System.Drawing.Color]::FromArgb(255, 0x20, 0x20, 0x20)
$Foreground = [System.Drawing.Color]::FromArgb(255, 0xFF, 0xFF, 0xFF)
$Glyph = 'Aя'
$bitmap = New-Object System.Drawing.Bitmap($Width, $Height, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
try {
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAliasGridFit
# Full bleed: Partner Center masks/rounds the poster itself, so the plate
# here just fills the whole canvas rather than drawing its own corners
$graphics.Clear($Background)
# The glyph is sized off the width, the narrower of the two dimensions,
# the same way New-Assets.ps1 sizes it off the shorter side of a square
[System.Drawing.StringFormat] $format = [System.Drawing.StringFormat]::GenericTypographic.Clone()
try {
$target = $Width * 0.62
$ceiling = $Height * 0.30
$fontSize = $Width * 0.9
$font = $null
$measured = $null
for ($attempt = 0; $attempt -lt 20; $attempt++) {
if ($font) { $font.Dispose() }
$font = New-Object System.Drawing.Font('Segoe UI', $fontSize, [System.Drawing.FontStyle]::Bold, [System.Drawing.GraphicsUnit]::Pixel)
$measured = $graphics.MeasureString($Glyph, $font, ([System.Drawing.PointF]::new(0, 0)), $format)
if ($measured.Width -le $target -and $measured.Height -le $ceiling) { break }
$scale = [Math]::Min($target / $measured.Width, $ceiling / $measured.Height)
$fontSize = $fontSize * $scale * 0.98
}
try {
$x = [Math]::Round(($Width - $measured.Width) / 2.0)
$y = [Math]::Round(($Height - $measured.Height) / 2.0)
$brush = New-Object System.Drawing.SolidBrush($Foreground)
$origin = New-Object System.Drawing.PointF($x, $y)
try { $graphics.DrawString($Glyph, $font, $brush, $origin, $format) }
finally { $brush.Dispose() }
}
finally {
$font.Dispose()
}
}
finally {
$format.Dispose()
}
}
finally {
$graphics.Dispose()
}
try { $bitmap.Save($OutPath, [System.Drawing.Imaging.ImageFormat]::Png) } finally { $bitmap.Dispose() }
Write-Host "Saved $OutPath"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -9,6 +9,9 @@
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:title" content="CursorLang — the keyboard layout at your cursor"> <meta property="og:title" content="CursorLang — the keyboard layout at your cursor">
<meta property="og:description" content="Switch the layout and a small popup names it, right where you are typing. Free, offline, under 9 MB in the tray."> <meta property="og:description" content="Switch the layout and a small popup names it, right where you are typing. Free, offline, under 9 MB in the tray.">
<meta property="og:url" content="https://cursor-lang.alrakis.kz/">
<meta property="og:image" content="https://cursor-lang.alrakis.kz/favicon.svg">
<link rel="canonical" href="https://cursor-lang.alrakis.kz/">
<meta name="theme-color" content="#fbfbfd" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#fbfbfd" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0d0d10" media="(prefers-color-scheme: dark)"> <meta name="theme-color" content="#0d0d10" media="(prefers-color-scheme: dark)">
<link rel="icon" href="favicon.svg" type="image/svg+xml"> <link rel="icon" href="favicon.svg" type="image/svg+xml">