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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Desktop;

public sealed partial class DesktopSession
{
    private static DesktopBounds DocumentBody(DesktopWindow window) => new(window.Bounds.X + 2, window.Bounds.Y + 67,
        Math.Max(1, window.Bounds.Width - 4), Math.Max(1, window.Bounds.Height - 96));

    private static DesktopBounds EditorViewport(DesktopWindow window)
    {
        var body = DocumentBody(window);
        return window.IsMarkdown && window.MarkdownView == MarkdownViewMode.Split
            ? body with { Width = Math.Max(1, (body.Width - 4) / 2) } : body;
    }

    private static DesktopBounds PreviewViewport(DesktopWindow window)
    {
        var body = DocumentBody(window);
        if (window.MarkdownView != MarkdownViewMode.Split) return body;
        int left = Math.Max(1, (body.Width - 4) / 2);
        return new(body.X + left + 4, body.Y, Math.Max(1, body.Width - left - 4), body.Height);
    }

    private static bool ShowsSource(DesktopWindow window) => !window.IsMarkdown || window.MarkdownView != MarkdownViewMode.Preview;
    private static bool ShowsPreview(DesktopWindow window) => window.IsMarkdown && window.MarkdownView != MarkdownViewMode.Edit;
    private static IInteractiveBrowserSession? FocusedPage(DesktopWindow window) => window.IsMarkdown && window.PreviewFocused ? window.PreviewSession : window.BrowserSession;
    private DesktopBounds MarkdownEditButton(DesktopWindow window) => new(window.Bounds.X + window.Bounds.Width - 98, window.Bounds.Y + 35, 92, 30);
    private DesktopBounds MarkdownSplitButton(DesktopWindow window) => new(window.Bounds.X + window.Bounds.Width - 194, window.Bounds.Y + 35, 92, 30);

    /// <summary>Mode changes retain the unsaved editor model and the independent preview page.</summary>
    public void SetMarkdownView(int windowId, MarkdownViewMode mode)
    {
        var window = _windows.Find(candidate => candidate.Id == windowId && candidate.IsMarkdown);
        if (window is null) return;
        if (window.BrowserSession is IBackgroundEditorSession background && mode != MarkdownViewMode.Edit)
        {
            window.Status = "Preparing preview after queued editor input...";
            if (!background.RequestEditorReadback((source, error) =>
            {
                if (!_windows.Contains(window)) return;
                if (source is null || error is not null)
                { window.Status = "Preview failed: " + error; InvalidateScene(window.Bounds); return; }
                if (window.Content != source) { window.Content = source; window.IsDirty = true; }
                SetMarkdownViewCore(window, mode);
            })) window.Status = "Preview could not be queued; retry when the editor worker is available.";
            InvalidateScene(window.Bounds);
            return;
        }
        if (window.BrowserSession?.ReadEditorText() is { } latest && latest != window.Content)
        { window.Content = latest; window.IsDirty = true; }
        SetMarkdownViewCore(window, mode);
    }

    private void SetMarkdownViewCore(DesktopWindow window, MarkdownViewMode mode)
    {
        CancelPointerCapture();
        (_webPointerCapturePreview ? _webPointerCapture?.PreviewSession : _webPointerCapture?.BrowserSession)?.Pointer(new("pointercancel", -1, -1));
        _webPointerCapture = _webHoveredWindow = null;
        window.MarkdownView = mode;
        window.PreviewFocused = mode == MarkdownViewMode.Preview;
        window.BrowserContentFocused = mode == MarkdownViewMode.Preview ? window.PreviewSession is not null : window.BrowserSession is not null;
        if (mode != MarkdownViewMode.Preview && window.BrowserSession is null && window.EditorPreparation is null)
        {
            if (_editorPreparation is not null) BeginEditorPreparation(window);
            // A missing worker still leaves a responsive basic source editor.
            else window.Status = "Text editor | Ctrl+S saves | Split shows the live document";
        }
        if (ShowsPreview(window) && window.PreviewSource != window.Content) BeginMarkdownPreview(window);
        ResizeDocumentPanes(window);
        InvalidateScene(window.Bounds);
    }

    private void ResizeDocumentPanes(DesktopWindow window)
    {
        if (!window.IsMarkdown) return;
        var source = EditorViewport(window); var preview = PreviewViewport(window);
        if (ShowsSource(window)) window.BrowserSession?.ResizeViewport(source.Width, source.Height);
        if (ShowsPreview(window)) window.PreviewSession?.ResizeViewport(preview.Width, preview.Height);
        window.PreviewScroll = Math.Clamp(window.PreviewScroll, 0, Math.Max(0, (window.PreviewSession?.DocumentHeight ?? 0) - preview.Height));
    }

    private void BeginMarkdownPreview(DesktopWindow window)
    {
        if (window.PreviewPreparation is { } pending)
        { RetireEditorPreparation(pending); window.PreviewPreparation = null; }
        var viewport = PreviewViewport(window);
        window.PreviewObservedContent = window.Content;
        window.PreviewError = null;
        window.PreviewRefreshAt = 0;
        window.Status = "Loading document preview in background...";
        if (_editorPreparation is not null)
        {
            var request = new EditorPreparationRequest(window.Id, ++_nextEditorPreparationGeneration, window.Content, window.Location,
                viewport.Width, viewport.Height, _monotonicMilliseconds?.Invoke() ?? _feedbackNow, EditorPreparationKind.Document, _theme.IsDark);
            window.PreviewPreparationGeneration = request.Generation;
            try { window.PreviewPreparation = _editorPreparation.Begin(request); }
            catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
            { window.PreviewSource = window.Content; window.PreviewError = error.Message; window.Status = "Document preview unavailable: " + error.Message; }
            return;
        }
        // Explicit synchronous adapters are retained for host tooling. The
        // native desktop always supplies the verified background service.
        if (_browser is not IInteractiveBrowserFactory factory)
        { window.PreviewSource = window.Content; window.PreviewError = "Document preview requires the web renderer"; window.Status = window.PreviewError; return; }
        IInteractiveBrowserSession? page = null;
        try
        {
            page = factory.CreatePageSession(); page.SetCanvas(_canvas); page.ResizeViewport(viewport.Width, viewport.Height);
            page.BeginDocumentPreview(window.Content, window.Location, _theme.IsDark);
            RetireBrowserSession(window.PreviewSession); window.PreviewSession = page; window.PreviewSource = window.Content;
            window.PreviewFocused = window.MarkdownView == MarkdownViewMode.Preview; window.BrowserContentFocused = window.PreviewFocused;
            window.Status = "Document preview | Edit or Split to change the source";
        }
        catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
        { RetireBrowserSession(page); window.PreviewSource = window.Content; window.PreviewError = error.Message; window.Status = "Document preview failed: " + error.Message; }
    }

    private void PollMarkdownPreviews(long now)
    {
        foreach (var window in _windows)
        {
            if (!window.IsMarkdown) continue;
            if (window.PreviewObservedContent != window.Content)
            { window.PreviewObservedContent = window.Content; window.PreviewRefreshAt = now + 180; }
            if (window.PreviewPreparation is { } operation && operation.TryTakeResult(out var result))
            {
                window.PreviewPreparation = null;
                if (operation.Request.Generation != window.PreviewPreparationGeneration || operation.Request.Source != window.Content || operation.Request.Path != window.Location || operation.Request.Dark != _theme.IsDark)
                {
                    RetireBrowserSession(result.Page); RetireEditorPreparation(operation);
                    window.PreviewRefreshAt = now + 180;
                }
                else if (result.Page is { } page)
                {
                    try
                    {
                        bool equivalent = result.PreparedCanvas is IEditorPreparationCanvasHandoff handoff && handoff.TryCompleteEditorPreparationHandoff(_canvas);
                        page.CompleteEditorPreparationHandoff(_canvas, equivalent);
                        RetireBrowserSession(window.PreviewSession); window.PreviewSession = page;
                        window.PreviewSource = operation.Request.Source;
                        ResizeDocumentPanes(window);
                        window.Status = "Document preview | Edit or Split to change the source";
                        if (window.MarkdownView == MarkdownViewMode.Preview) { window.PreviewFocused = true; window.BrowserContentFocused = true; }
                    }
                    catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
                    { RetireBrowserSession(page); window.PreviewSource = operation.Request.Source; window.PreviewError = error.Message; window.Status = "Document preview failed: " + error.Message; }
                }
                else { window.PreviewSource = operation.Request.Source; window.PreviewError = result.Error; window.Status = "Document preview failed: " + result.Error; }
                InvalidateScene(window.Bounds);
            }
            if (ShowsPreview(window) && window.PreviewSource != window.Content && window.PreviewPreparation is null)
            {
                if (window.PreviewRefreshAt == 0) window.PreviewRefreshAt = now + 180;
                else if (now >= window.PreviewRefreshAt) BeginMarkdownPreview(window);
            }
            else if (window.PreviewSource == window.Content) window.PreviewRefreshAt = 0;
            if (ShowsPreview(window) && window.PreviewSession is { } preview && (!window.IsMinimized || preview is IBackgroundEditorSession) && preview.AdvanceTime(now))
                InvalidateScene(PreviewViewport(window));
        }
    }

    private void DrawMarkdownToolbar(DesktopWindow window)
    {
        var b = window.Bounds;
        Button(HitKind.EditorSave, window.Id, 0, new(b.X + 4, b.Y + 35, 84, 30), Panel);
        DrawIcon(DesktopIcon.TextFile, b.X + 12, b.Y + 39, 18); _canvas.Text("Save", TextColor, b.X + 34, b.Y + 43);
        var split = MarkdownSplitButton(window); var edit = MarkdownEditButton(window);
        Button(HitKind.EditorSplit, window.Id, 0, split, window.MarkdownView == MarkdownViewMode.Split ? _theme.Selection : Panel);
        Button(HitKind.EditorEdit, window.Id, 0, edit, Panel);
        _canvas.Text("Split", TextColor, split.X + 25, split.Y + 8);
        _canvas.Text(window.MarkdownView == MarkdownViewMode.Preview ? "Edit" : "Preview", TextColor, edit.X + 16, edit.Y + 8);
    }
}