XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text.Json;

namespace XFESystem.Desktop;

public sealed partial class DesktopSession
{
    public const string DisplaySettingsPath = DesktopTheme.SettingsDirectory + "\\Displays.json";
    private readonly IDesktopDisplayManager? _displays;
    private DesktopDisplayConfiguration? _displayDraft, _displayPrevious;
    private string? _displayDrag;
    private int _displayDragX, _displayDragY, _displayDragPositionX, _displayDragPositionY;
    private double _displayDragScale;
    private long _displayRevertAt, _displayIdentifyUntil;
    private int _displayCountdown;
    public bool IsDisplayChangePending => _displayPrevious is not null;
    public string DisplayStatus { get; private set; } = "Select a display to change its settings";
    private DesktopBounds TaskbarBounds => new(WorkArea.X, WorkArea.Y + WorkArea.Height, WorkArea.Width, TaskbarHeight);
    private DesktopBounds VirtualWindowArea => _displays?.Current.Mode == DesktopDisplayMode.Extend && _displays.Layout.Screens.Count > 1
        ? new(0, 0, _canvas.Width, _canvas.Height) : WorkArea;
    private static DesktopBounds DisplayWorkArea(DesktopBounds screen, bool primary) => screen with { Height = Math.Max(1, screen.Height - (primary ? TaskbarHeight : 0)) };

    private DesktopBounds MonitorWorkArea(DesktopBounds window)
    {
        if (_displays is null) return WorkArea;
        var selected = _displays.Layout.Screens.MaxBy(s =>
        {
            var b = s.Bounds;
            return (long)Math.Max(0, Math.Min(window.X + window.Width, b.X + b.Width) - Math.Max(window.X, b.X)) *
                Math.Max(0, Math.Min(window.Y + window.Height, b.Y + b.Height) - Math.Max(window.Y, b.Y));
        })!;
        return DisplayWorkArea(selected.Bounds, selected.IsPrimary);
    }

    private void LoadDisplaySettings()
    {
        try
        {
            if (!_storage.Exists(DisplaySettingsPath)) return;
            var saved = DesktopDisplayConfiguration.Parse(_storage.ReadText(DisplaySettingsPath));
            if (!_displays!.TryApply(saved, out string? error)) DisplayStatus = "Saved layout no longer matches the displays: " + error;
            else DisplayStatus = "Saved display layout restored";
        }
        catch (Exception error) when (error is IOException or UnauthorizedAccessException or FormatException or JsonException or InvalidOperationException or OverflowException)
        { DisplayStatus = "Display settings could not be restored; using the startup display: " + error.Message; }
    }

    private void DisplayLayoutChanged()
    {
        CancelPointerCapture(); IsStartMenuOpen = false; _switchOrder = null;
        foreach (var window in _windows)
        {
            var area = MonitorWorkArea(window.Bounds);
            if (window.IsMaximized) window.Bounds = area;
            else
            {
                window.Bounds = Constrain(window.Bounds with { Width = Math.Min(window.Bounds.Width, area.Width), Height = Math.Min(window.Bounds.Height, area.Height) });
                window.RestoreBounds = Constrain(window.RestoreBounds);
                window.IsSnapped = false;
            }
            foreach (var page in new[] { window.BrowserSession, window.PreviewSession }) page?.SetCanvas(_canvas);
            if (window.BrowserSession is { } browser)
            { var viewport = BrowserViewport(window); browser.ResizeViewport(viewport.Width, viewport.Height); }
            if (window.PreviewSession is { } preview)
            { var viewport = PreviewViewport(window); preview.ResizeViewport(viewport.Width, viewport.Height); }
        }
        if (_displays is not null) (_pointerX, _pointerY) = _displays.Layout.ConstrainPointer(_pointerX, _pointerY);
        _hovered = _pressed = null;
        NeedsRender = true;
    }

    public bool ApplyDisplayConfiguration(DesktopDisplayConfiguration configuration)
    {
        if (_displays is null) { SetDisplayStatus("This runtime has no display management adapter"); return false; }
        if (IsDisplayChangePending) { SetDisplayStatus("Keep or revert the current change first"); return false; }
        var previous = _displays.Current;
        if (!_displays.TryApply(configuration, out string? error)) { SetDisplayStatus(error ?? "Display change failed"); return false; }
        _displayPrevious = previous; _displayDraft = configuration;
        _displayRevertAt = (_monotonicMilliseconds?.Invoke() ?? _feedbackNow) + 15000;
        _displayCountdown = 15;
        SetDisplayStatus("Keep these display settings? Reverting in 15 seconds");
        return true;
    }

    public void KeepDisplayConfiguration()
    {
        if (!IsDisplayChangePending || _displays is null) return;
        // Write only confirmed settings; a crash during the preview restores the last good layout.
        try
        {
            _storage.CreateDirectory(DesktopTheme.SettingsDirectory);
            _storage.WriteText(DisplaySettingsPath, _displays.Current.ToJson());
            SetDisplayStatus(_storage.IsPersistent ? "Display settings saved to disk" : "Display settings saved for this RAM session");
        }
        catch (Exception error) when (error is IOException or UnauthorizedAccessException or InvalidOperationException)
        { SetDisplayStatus("Display settings kept for this session; saving failed: " + error.Message); }
        _displayPrevious = null; _displayRevertAt = 0;
        NeedsRender = true;
    }

    public void RevertDisplayConfiguration()
    {
        if (_displayPrevious is not { } previous || _displays is null) return;
        if (!_displays.TryApply(previous, out string? error)) { SetDisplayStatus("Could not restore the previous layout: " + error); return; }
        _displayPrevious = null; _displayRevertAt = 0; _displayDraft = previous;
        SetDisplayStatus("Previous display settings restored");
    }

    private void SetDisplayStatus(string text)
    {
        DisplayStatus = text; Notification = text;
        foreach (var window in _windows.Where(w => w.IsDisplaySettings)) window.Status = text;
        NeedsRender = true;
    }

    private void AdvanceDisplaySettings(long now)
    {
        if (_displayIdentifyUntil != 0 && now >= _displayIdentifyUntil)
        { _displayIdentifyUntil = 0; _displays?.Identify(false); NeedsRender = true; }
        if (!IsDisplayChangePending) return;
        if (now >= _displayRevertAt) { RevertDisplayConfiguration(); return; }
        int seconds = (int)((_displayRevertAt - now + 999) / 1000);
        if (seconds != _displayCountdown)
        { _displayCountdown = seconds; SetDisplayStatus("Keep these display settings? Reverting in " + seconds + " seconds"); }
    }

    public void ShowDisplaySettings()
    {
        OpenSettings();
        if (ActiveWindow is not { Application: DesktopApplication.Settings } window) return;
        window.IsDisplaySettings = true; window.DisplayModeMenu = false; window.Title = "Settings - System > Display";
        window.Bounds = Constrain(window.Bounds with { Width = Math.Max(window.Bounds.Width, Math.Min(800, WorkArea.Width)), Height = Math.Max(window.Bounds.Height, Math.Min(600, WorkArea.Height)) });
        window.RestoreBounds = window.Bounds;
        _displayDraft ??= _displays?.Current;
        window.Status = DisplayStatus;
        NeedsRender = true;
    }

    private bool SettingsPageClick(DesktopWindow window, int x, int y)
    {
        if (y is < 40 or >= 70) return false;
        if (!window.IsDisplaySettings && x >= window.Bounds.Width - 152 && x < window.Bounds.Width - 24)
        { ShowDisplaySettings(); return true; }
        if (window.IsDisplaySettings && x is >= 24 and < 152)
        { window.IsDisplaySettings = false; window.DisplayModeMenu = false; window.Title = "Settings - Appearance"; window.Status = AppearanceStatus; NeedsRender = true; return true; }
        return false;
    }
    private HitTarget? ResolveSettingsPageTarget(DesktopWindow window, int x, int y)
    {
        var b = window.Bounds;
        var bounds = new DesktopBounds(b.X + (window.IsDisplaySettings ? 24 : b.Width - 152), b.Y + 40, 128, 30);
        return bounds.Contains(x, y) ? new(window.IsDisplaySettings ? HitKind.SettingsAppearance : HitKind.SettingsDisplay, window.Id, 0, bounds) : null;
    }

    private DesktopBounds DisplayDiagram(DesktopWindow window) => new(window.Bounds.X + 24, window.Bounds.Y + 100,
        Math.Max(100, window.Bounds.Width - 48), Math.Clamp(window.Bounds.Height - 385, 110, 220));
    private int DisplayControlsY(DesktopWindow window) => DisplayDiagram(window).Y + DisplayDiagram(window).Height + 12;
    private IEnumerable<HitTarget> DisplayControls(DesktopWindow window)
    {
        var b = window.Bounds; int y = DisplayControlsY(window);
        yield return new(HitKind.DisplayIdentify, window.Id, 0, new(b.X + 24, y, 100, 32));
        yield return new(HitKind.DisplayDetect, window.Id, 0, new(b.X + 136, y, 100, 32));
        yield return new(HitKind.DisplayMode, window.Id, 0, new(b.X + 248, y, Math.Max(112, b.Width - 272), 32));
        yield return new(HitKind.DisplayPrimary, window.Id, 0, new(b.X + 24, y + 76, Math.Min(300, b.Width - 48), 32));
        if (IsDisplayChangePending)
        {
            yield return new(HitKind.DisplayKeep, window.Id, 0, new(b.X + 24, b.Y + b.Height - 74, 148, 34));
            yield return new(HitKind.DisplayRevert, window.Id, 0, new(b.X + 184, b.Y + b.Height - 74, 112, 34));
        }
        else yield return new(HitKind.DisplayApply, window.Id, 0, new(b.X + 24, b.Y + b.Height - 74, 148, 34));
    }
    private IEnumerable<HitTarget> DisplayModeOptions(DesktopWindow window)
    {
        var mode = DisplayControls(window).First(t => t.Kind == HitKind.DisplayMode);
        for (int i = 0; i < 3; i++) yield return new(HitKind.DisplayModeOption, window.Id, i, mode.Bounds with { Y = mode.Bounds.Y + 34 + i * 34, Height = 34 });
    }
    private (DesktopDisplay Display, DesktopBounds Bounds, double Scale)[] DisplayTiles(DesktopWindow window)
    {
        if (_displays is null || _displayDraft is null) return [];
        // The arrangement remains visible in Duplicate mode, ready for switching to Extend.
        var positions = _displayDraft.Positions;
        int left = positions.Min(p => p.X), top = positions.Min(p => p.Y);
        int right = positions.Max(p => p.X + _displays.Displays.Single(d => d.Id == p.Id).NativeWidth * 100 / _displays.ScalePercent);
        int bottom = positions.Max(p => p.Y + _displays.Displays.Single(d => d.Id == p.Id).NativeHeight * 100 / _displays.ScalePercent);
        var area = DisplayDiagram(window);
        double scale = Math.Min((area.Width - 36d) / Math.Max(1, right - left), (area.Height - 30d) / Math.Max(1, bottom - top));
        double ox = area.X + (area.Width - (right - left) * scale) / 2, oy = area.Y + (area.Height - (bottom - top) * scale) / 2;
        return _displays.Displays.Select(d =>
        {
            var p = positions.Single(p => p.Id == d.Id);
            return (d, new DesktopBounds((int)(ox + (p.X - left) * scale), (int)(oy + (p.Y - top) * scale),
                Math.Max(10, (int)(d.NativeWidth * 100d / _displays.ScalePercent * scale) - 3), Math.Max(10, (int)(d.NativeHeight * 100d / _displays.ScalePercent * scale) - 3)), scale);
        }).ToArray();
    }
    private HitTarget? ResolveDisplayTarget(DesktopWindow window, int x, int y)
    {
        if (_displays is null) return null;
        if (window.DisplayModeMenu)
            foreach (var option in DisplayModeOptions(window)) if (option.Bounds.Contains(x, y)) return option;
        foreach (var control in DisplayControls(window)) if (control.Bounds.Contains(x, y)) return control;
        var tiles = DisplayTiles(window);
        for (int i = 0; i < tiles.Length; i++) if (tiles[i].Bounds.Contains(x, y)) return new(HitKind.DisplayMonitor, window.Id, i, tiles[i].Bounds);
        return null;
    }
    private void DisplaySettingsClick(DesktopWindow window, int x, int y)
    {
        if (_displays is null || _displayDraft is null) return;
        var target = ResolveDisplayTarget(window, x + window.Bounds.X, y + window.Bounds.Y);
        if (target is not { } hit) { window.DisplayModeMenu = false; NeedsRender = true; return; }
        if (hit.Kind == HitKind.DisplayMonitor)
        {
            window.SelectedDisplay = hit.Index;
            if (!IsDisplayChangePending && _displayDraft.Mode == DesktopDisplayMode.Extend)
            {
                _displayDrag = _displays.Displays[hit.Index].Id;
                var p = _displayDraft.Positions.Single(p => p.Id == _displayDrag);
                _displayDragX = x + window.Bounds.X; _displayDragY = y + window.Bounds.Y;
                _displayDragPositionX = p.X; _displayDragPositionY = p.Y; _displayDragScale = DisplayTiles(window)[hit.Index].Scale;
            }
        }
        else if (hit.Kind == HitKind.DisplayIdentify)
        { _displays.Identify(true); _displayIdentifyUntil = _feedbackNow + 4000; SetDisplayStatus("Display numbers are shown for four seconds"); }
        else if (hit.Kind == HitKind.DisplayDetect) { _displays.Detect(); SetDisplayStatus(_displays.Displays.Count + " display output(s). " + _displays.DetectionMessage); }
        else if (hit.Kind == HitKind.DisplayKeep) KeepDisplayConfiguration();
        else if (hit.Kind == HitKind.DisplayRevert) RevertDisplayConfiguration();
        else if (!IsDisplayChangePending)
        {
            if (hit.Kind == HitKind.DisplayMode) window.DisplayModeMenu = !window.DisplayModeMenu;
            if (hit.Kind == HitKind.DisplayModeOption) { _displayDraft = _displayDraft.WithMode((DesktopDisplayMode)hit.Index); window.DisplayModeMenu = false; }
            if (hit.Kind == HitKind.DisplayPrimary) _displayDraft = _displayDraft.WithPrimary(_displays.Displays[window.SelectedDisplay].Id);
            if (hit.Kind == HitKind.DisplayApply) ApplyDisplayConfiguration(_displayDraft);
        }
        NeedsRender = true;
    }

    private void MoveDisplayDiagram(int x, int y)
    {
        if (_displayDrag is null || _displays is null || _displayDraft is null) return;
        int requestedX = Math.Clamp(_displayDragPositionX + (int)((x - _displayDragX) / _displayDragScale), -16384, 16384);
        int requestedY = Math.Clamp(_displayDragPositionY + (int)((y - _displayDragY) / _displayDragScale), -16384, 16384);
        var display = _displays.Displays.Single(d => d.Id == _displayDrag);
        int width = display.NativeWidth * 100 / _displays.ScalePercent, height = display.NativeHeight * 100 / _displays.ScalePercent;
        var candidates = new List<(int X, int Y)>();
        foreach (var p in _displayDraft.Positions.Where(p => p.Id != _displayDrag))
        {
            var other = _displays.Displays.Single(d => d.Id == p.Id);
            int w = other.NativeWidth * 100 / _displays.ScalePercent, h = other.NativeHeight * 100 / _displays.ScalePercent;
            int alignedX = Math.Abs(requestedX - p.X) < 120 ? p.X : Math.Clamp(requestedX, p.X - width + 1, p.X + w - 1);
            int alignedY = Math.Abs(requestedY - p.Y) < 120 ? p.Y : Math.Clamp(requestedY, p.Y - height + 1, p.Y + h - 1);
            candidates.Add((p.X - width, alignedY)); candidates.Add((p.X + w, alignedY));
            candidates.Add((alignedX, p.Y - height)); candidates.Add((alignedX, p.Y + h));
        }
        foreach (var candidate in candidates.OrderBy(p => Math.Pow(p.X - requestedX, 2) + Math.Pow(p.Y - requestedY, 2)))
        {
            var next = _displayDraft.Move(_displayDrag, candidate.X, candidate.Y);
            try { DesktopDisplayLayout.Create(_displays.Displays, next, _displays.ScalePercent); }
            catch (ArgumentException) { continue; }
            _displayDraft = next; NeedsRender = true; break;
        }
    }

    private static string DisplayModeLabel(DesktopDisplayMode mode) => mode switch
    { DesktopDisplayMode.Duplicate => "Duplicate these displays", DesktopDisplayMode.Extend => "Extend these displays", _ => "Show only the main display" };
    private void DrawDisplaySettings(DesktopWindow window)
    {
        var b = window.Bounds;
        Button(HitKind.SettingsAppearance, window.Id, 0, new(b.X + 24, b.Y + 40, 128, 30), _theme.SoftAccent);
        _canvas.Text("< Appearance", White, b.X + 36, b.Y + 49);
        _canvas.Text("System > Display", TextColor, b.X + 174, b.Y + 49);
        _canvas.Text("Select a display. Drag its tile to arrange in Extend mode.", Muted, b.X + 24, b.Y + 80);
        if (_displays is null || _displayDraft is null) { _canvas.Text("Display management is unavailable in this runtime.", Muted, b.X + 24, b.Y + 118); return; }
        var diagram = DisplayDiagram(window);
        RoundedFill(_theme.Surface, diagram.X, diagram.Y, diagram.Width, diagram.Height, 10);
        var tiles = DisplayTiles(window);
        window.SelectedDisplay = Math.Clamp(window.SelectedDisplay, 0, tiles.Length - 1);
        for (int i = 0; i < tiles.Length; i++)
        {
            var tile = tiles[i];
            Button(HitKind.DisplayMonitor, window.Id, i, tile.Bounds, i == window.SelectedDisplay ? _theme.AccentStroke : _theme.Border, 5);
            uint ink = i == window.SelectedDisplay ? (_theme.IsDark ? 0x08131bu : 0xffffffu) : White;
            if (_canvas is IDesktopWebCanvas web)
                web.WebText((i + 1).ToString(), ink, tile.Bounds.X + tile.Bounds.Width / 2d - 10, tile.Bounds.Y + tile.Bounds.Height / 2d - 22, 36, "Lato, sans-serif", 700);
            else _canvas.Text((i + 1).ToString(), ink, tile.Bounds.X + tile.Bounds.Width / 2 - 4, tile.Bounds.Y + tile.Bounds.Height / 2 - 6);
        }
        foreach (var control in DisplayControls(window))
        {
            if (control.Bounds.Y + control.Bounds.Height > b.Y + b.Height - 28) continue;
            bool selected = control.Kind == HitKind.DisplayPrimary && _displayDraft.PrimaryId == tiles[window.SelectedDisplay].Display.Id;
            Button(control.Kind, window.Id, control.Index, control.Bounds, selected ? _theme.Selection : _theme.SoftAccent);
            string label = control.Kind switch
            {
                HitKind.DisplayIdentify => "Identify", HitKind.DisplayDetect => "Detect",
                HitKind.DisplayMode => DisplayModeLabel(_displayDraft.Mode) + "  v",
                HitKind.DisplayPrimary => selected ? "This is the main display" : "Make this my main display",
                HitKind.DisplayKeep => "Keep changes", HitKind.DisplayRevert => "Revert", _ => "Apply changes"
            };
            _canvas.Text(Clip(label, (control.Bounds.Width - 20) / 8), White, control.Bounds.X + 10, control.Bounds.Y + 10);
        }
        int y = DisplayControlsY(window); var display = tiles[window.SelectedDisplay].Display;
        _canvas.Text(display.Name + "  |  " + display.NativeWidth + " x " + display.NativeHeight + "  |  Scale " + _displays.ScalePercent + "%", TextColor, b.X + 24, y + 51);
        void Label(string text, int offset)
        { if (y + offset + 20 < b.Y + b.Height - 86) _canvas.Text(Clip(text, (b.Width - 48) / 8), Muted, b.X + 24, y + offset); }
        Label(_displayDraft.Mode switch { DesktopDisplayMode.Duplicate => "The same desktop appears on every display; aspect ratio is preserved.",
            DesktopDisplayMode.Extend => "Move windows and the pointer across adjoining display edges.", _ => "Only the main display shows the desktop. Other outputs are black." }, 122);
        Label("Resolution and scale follow the startup framebuffer for this driver.", 146);
        Label("Connect displays before startup. Extra GPU ports need a display driver.", 170);
        if (IsDisplayChangePending) _canvas.Text("Reverting in " + _displayCountdown + "s", TextColor, b.X + 316, b.Y + b.Height - 63);
        if (window.DisplayModeMenu)
            foreach (var option in DisplayModeOptions(window))
            {
                Button(option.Kind, window.Id, option.Index, option.Bounds, _displayDraft.Mode == (DesktopDisplayMode)option.Index ? _theme.Selection : Panel);
                _canvas.Text(Clip(DisplayModeLabel((DesktopDisplayMode)option.Index), (option.Bounds.Width - 20) / 8), White, option.Bounds.X + 10, option.Bounds.Y + 10);
            }
    }

    private void DisplaySettingsKey(DesktopWindow window, DesktopKey key, char character)
    {
        if (_displays is null || _displayDraft is null) return;
        char action = char.ToLowerInvariant(character);
        if (key == DesktopKey.Tab) { window.SelectedDisplay = (window.SelectedDisplay + 1) % _displays.Displays.Count; NeedsRender = true; return; }
        if (key == DesktopKey.Character && action is >= '1' and <= '8') window.SelectedDisplay = Math.Min(action - '1', _displays.Displays.Count - 1);
        else if (key is DesktopKey.Left or DesktopKey.Right) window.SelectedDisplay = (window.SelectedDisplay + (key == DesktopKey.Right ? 1 : -1) + _displays.Displays.Count) % _displays.Displays.Count;
        else if (key == DesktopKey.Enter || key == DesktopKey.Save) { if (IsDisplayChangePending) KeepDisplayConfiguration(); else ApplyDisplayConfiguration(_displayDraft); }
        else if (key == DesktopKey.Character && action == 'k') KeepDisplayConfiguration();
        else if (key == DesktopKey.Character && action == 'r') RevertDisplayConfiguration();
        else if (key == DesktopKey.Character && action == 'm' && !IsDisplayChangePending) _displayDraft = _displayDraft.WithMode((DesktopDisplayMode)(((int)_displayDraft.Mode + 1) % 3));
        else if (key == DesktopKey.Character && action == 'p' && !IsDisplayChangePending) _displayDraft = _displayDraft.WithPrimary(_displays.Displays[window.SelectedDisplay].Id);
        else if (key == DesktopKey.Character && action == 'i')
        { _displays.Identify(true); _displayIdentifyUntil = _feedbackNow + 4000; SetDisplayStatus("Display numbers are shown for four seconds"); }
        else if (key == DesktopKey.Character && action == 'd') { _displays.Detect(); SetDisplayStatus(_displays.DetectionMessage); }
        else if (key == DesktopKey.Character && action == 'a') { window.IsDisplaySettings = false; window.Title = "Settings - Appearance"; }
        NeedsRender = true;
    }

    public string DisplayStateReport => _displays is null ? "" : "XFE_DESKTOP_DISPLAYS|Mode|" + _displays.Current.Mode + "|Outputs|" + _displays.Displays.Count +
        "|Width|" + _canvas.Width + "|Height|" + _canvas.Height + "|Primary|" + (_displays.Displays.ToList().FindIndex(d => d.Id == _displays.Current.PrimaryId) + 1) +
        "|Pending|" + (IsDisplayChangePending ? 1 : 0) + "|Identify|" + (_displayIdentifyUntil != 0 ? 1 : 0) + "\n";
}