From 727dda26235ecb57829d97667e2305610b5185bf Mon Sep 17 00:00:00 2001 From: Ad5001 Date: Sun, 27 Oct 2024 02:40:42 +0200 Subject: [PATCH] Adding loading screen for rendering LaTeX formula when threaded setting is enabled. --- common/src/history/common.mjs | 8 +- common/src/module/canvas.mjs | 3 + common/src/module/latex.mjs | 33 +++++- common/src/preferences/general.mjs | 4 +- common/test/mock/helper.mjs | 4 +- .../LogarithmPlotter/LogarithmPlotter.qml | 18 ++- .../ObjectLists/ObjectLists.qml | 2 +- .../LogarithmPlotter/Overlay/Loading.qml | 111 ++++++++++++++++++ .../Overlay/ViewPositionChange.qml | 10 -- .../eu/ad5001/LogarithmPlotter/Overlay/qmldir | 1 + .../LogarithmPlotter/util/config.py | 2 +- .../LogarithmPlotter/util/debug.py | 4 +- .../LogarithmPlotter/util/latex.py | 2 +- 13 files changed, 168 insertions(+), 34 deletions(-) diff --git a/common/src/history/common.mjs b/common/src/history/common.mjs index 33f9118..e8e5795 100644 --- a/common/src/history/common.mjs +++ b/common/src/history/common.mjs @@ -95,11 +95,15 @@ export class Action { if(!Latex.enabled) throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.") const imgDepth = History.imageDepth - const { source, width, height } = await Latex.requestAsyncRender( + const renderArguments = [ latexString, imgDepth * (History.fontSize + 2), History.themeTextColor - ) + ] + let render = Latex.findPrerendered(...renderArguments) + if(render === null) + render = await Latex.requestAsyncRender(...renderArguments) + const { source, width, height } = render return `` } diff --git a/common/src/module/canvas.mjs b/common/src/module/canvas.mjs index ba6d5ca..5736cfc 100644 --- a/common/src/module/canvas.mjs +++ b/common/src/module/canvas.mjs @@ -25,7 +25,10 @@ import Objects from "./objects.mjs" import History from "./history.mjs" import Settings from "./settings.mjs" + class CanvasAPI extends Module { + + /** @type {CanvasInterface} */ #canvas = null /** @type {CanvasRenderingContext2D} */ diff --git a/common/src/module/latex.mjs b/common/src/module/latex.mjs index 216e3be..65ce689 100644 --- a/common/src/module/latex.mjs +++ b/common/src/module/latex.mjs @@ -17,6 +17,7 @@ */ import { Module } from "./common.mjs" +import { BaseEvent } from "../events.mjs" import * as Instruction from "../lib/expr-eval/instruction.mjs" import { escapeValue } from "../lib/expr-eval/expression.mjs" import { HelperInterface, LatexInterface } from "./interface.mjs" @@ -44,6 +45,28 @@ const equivalchars = ["\\pi", "\\infty", "{}_{4}", "{}_{5}", "{}_{6}", "{}_{7}", "{}_{8}", "{}_{9}", "{}_{0}", ] + + + +class AsyncRenderStartedEvent extends BaseEvent { + constructor(markup, fontSize, color) { + super("async-render-started") + this.markup = markup + this.fontSize = fontSize + this.color = color + } +} + + +class AsyncRenderFinishedEvent extends BaseEvent { + constructor(markup, fontSize, color) { + super("async-render-finished") + this.markup = markup + this.fontSize = fontSize + this.color = color + } +} + /** * Class containing the result of a LaTeX render. * @@ -60,6 +83,8 @@ class LatexRenderResult { } class LatexAPI extends Module { + static emits = ["async-render-started", "async-render-finished"] + /** @type {LatexInterface} */ #latex = null @@ -113,10 +138,14 @@ class LatexAPI extends Module { async requestAsyncRender(markup, fontSize, color) { if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!") let render - if(this.#latex.supportsAsyncRender) + if(this.#latex.supportsAsyncRender) { + console.trace() + this.emit(new AsyncRenderStartedEvent(markup, fontSize, color)) render = await this.#latex.renderAsync(markup, fontSize, color) - else + this.emit(new AsyncRenderFinishedEvent(markup, fontSize, color)) + } else { render = this.#latex.renderSync(markup, fontSize, color) + } const args = render.split(",") return new LatexRenderResult(...args) } diff --git a/common/src/preferences/general.mjs b/common/src/preferences/general.mjs index a6957c6..a00a813 100644 --- a/common/src/preferences/general.mjs +++ b/common/src/preferences/general.mjs @@ -47,8 +47,8 @@ class EnableLatex extends BoolSetting { } const ENABLE_LATEX_ASYNC = new BoolSetting( - qsTranslate("general", "Enable asynchronous LaTeX renderer"), - "enable_latex_async", + qsTranslate("general", "Enable threaded LaTeX renderer (experimental)"), + "enable_latex_threaded", "new" ) diff --git a/common/test/mock/helper.mjs b/common/test/mock/helper.mjs index 4cab472..b912b44 100644 --- a/common/test/mock/helper.mjs +++ b/common/test/mock/helper.mjs @@ -23,7 +23,7 @@ const DEFAULT_SETTINGS = { "reset_redo_stack": true, "last_install_greet": "0", "enable_latex": true, - "enable_latex_async": true, + "enable_latex_threaded": true, "expression_editor": { "autoclose": true, "colorize": true, @@ -113,4 +113,4 @@ export class MockHelper { throw new Error(`File not found.`) } -} \ No newline at end of file +} diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml index b8799de..064a0be 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml @@ -17,10 +17,10 @@ */ import QtQml -import QtQuick.Controls -import eu.ad5001.MixedMenu 1.1 -import QtQuick.Layouts 1.12 import QtQuick +import QtQuick.Controls +import QtQuick.Layouts 1.12 +import eu.ad5001.MixedMenu 1.1 // Auto loading all modules. import eu.ad5001.LogarithmPlotter.Common @@ -158,21 +158,17 @@ ApplicationWindow { Overlay.ViewPositionChange { id: viewPositionChanger anchors.fill: parent - canvas: parent - settingsInstance: settings } Overlay.PickLocation { id: positionPicker anchors.fill: parent - canvas: parent } + } - // Overlay.Loading { - // id: loadingOverlay - // anchors.fill: parent - // canvas: parent - // } + Overlay.Loading { + id: loadingOverlay + anchors.fill: parent } Timer { diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml index f755a6f..c2a5a23 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml @@ -56,7 +56,7 @@ ScrollView { property var editingRows: [] model: Modules.Objects.currentObjects[objType] width: objectsListView.width - implicitHeight: contentItem.childrenRect.height + height: contentItem.childrenRect.height + 10 visible: model != undefined && model.length > 0 interactive: false diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/Loading.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/Loading.qml index a69f2cd..6aec616 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/Loading.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/Loading.qml @@ -17,3 +17,114 @@ */ import QtQuick +import QtQuick.Controls + + +/*! + \qmltype Loading + \inqmlmodule eu.ad5001.LogarithmPlotter.Overlay + \brief Overlay notifiying the user when a file is loading. + + Provides an overlay over the canvas that is shown when the user loads a new file, both to lock the ViewPositionChange + overlay and inform the user of what is loading and how much remains. + + \sa Common, ViewPositionChange +*/ +Item { + id: loadingRoot + opacity: 0 + visible: opacity !== 0 + clip: true + + property int currentlyLoading: 0 + property int maxCurrentLoadingSteps: 0 + + Behavior on opacity { PropertyAnimation {} } + + Rectangle { + anchors.fill: parent + color: sysPalette.window + opacity: 0.85 + } + + Column { + spacing: 5 + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + right: parent.right + } + + Text { + id: loadingTitle + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: 20 + color: sysPalette.windowText + } + + ProgressBar { + id: progress + anchors.horizontalCenter: parent.horizontalCenter + width: 300 + from: 0 + value: loadingRoot.maxCurrentLoadingSteps - loadingRoot.currentlyLoading + to: loadingRoot.maxCurrentLoadingSteps + } + + Text { + id: lastFinishedStep + anchors.horizontalCenter: parent.horizontalCenter + color: sysPalette.windowText + } + } + + MouseArea { + id: picker + anchors.fill: parent + hoverEnabled: parent.visible + cursorShape: Qt.ArrowCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + } + + + + /*! + \qmlmethod void Loading::addedLoadingStep() + Registers one new loading step that will eventually call \c finishedLoadingStep. + */ + function addedLoadingStep() { + if(loadingRoot.maxCurrentLoadingSteps === 1) { + // Only when several ones need to be loaded. + const fileName = Modules.Settings.saveFilename.split('/').pop().split('\\').pop() + loadingTitle.text = qsTr("Loading...") + loadingRoot.opacity = 1 + } + loadingRoot.currentlyLoading++ + loadingRoot.maxCurrentLoadingSteps++ + } + + /*! + \qmlmethod void Loading::finishedLoadingStep() + Marks a loading step as finished and displays the message to the user. + */ + function finishedLoadingStep(message) { + loadingRoot.currentlyLoading-- + const current = loadingRoot.maxCurrentLoadingSteps - loadingRoot.currentlyLoading + lastFinishedStep.text = `${message} (${current}/${loadingRoot.maxCurrentLoadingSteps})` + if(loadingRoot.currentlyLoading === 0) { + loadingRoot.maxCurrentLoadingSteps = 0 + loadingRoot.opacity = 0 + } + } + + + Component.onCompleted: function() { + Modules.Latex.on("async-render-started", (e) => { + addedLoadingStep() + }) + Modules.Latex.on("async-render-finished", (e) => { + const markup = e.markup.length > 20 ? e.markup.substring(0, 15)+"..." : e.markup + finishedLoadingStep(qsTr("Finished rendering of %1").arg(markup)) + }) + } +} diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/ViewPositionChange.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/ViewPositionChange.qml index 1931536..41ce60a 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/ViewPositionChange.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/ViewPositionChange.qml @@ -57,16 +57,6 @@ Item { */ signal endPositionChange(int deltaX, int deltaY) - /*! - \qmlproperty var ViewPositionChangeOverlay::canvas - LogGraphCanvas instance. - */ - property var canvas - /*! - \qmlproperty var ViewPositionChangeOverlay::settingsInstance - Settings instance. - */ - property var settingsInstance /*! \qmlproperty int ViewPositionChangeOverlay::prevX The x coordinate (on the mousearea) at the last change of the canvas position. diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/qmldir index 8daae21..0288c9e 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/qmldir +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/qmldir @@ -1,4 +1,5 @@ module eu.ad5001.LogarithmPlotter.Overlay +Loading 1.0 Loading.qml PickLocation 1.0 PickLocation.qml ViewPositionChange 1.0 ViewPositionChange.qml diff --git a/runtime-pyside6/LogarithmPlotter/util/config.py b/runtime-pyside6/LogarithmPlotter/util/config.py index e01deae..020569a 100644 --- a/runtime-pyside6/LogarithmPlotter/util/config.py +++ b/runtime-pyside6/LogarithmPlotter/util/config.py @@ -28,7 +28,7 @@ DEFAULT_SETTINGS = { "reset_redo_stack": True, "last_install_greet": "0", "enable_latex": which("latex") is not None and which("dvipng") is not None, - "enable_latex_async": True, + "enable_latex_threaded": True, "expression_editor": { "autoclose": True, "colorize": True, diff --git a/runtime-pyside6/LogarithmPlotter/util/debug.py b/runtime-pyside6/LogarithmPlotter/util/debug.py index f899fd5..ef02d62 100644 --- a/runtime-pyside6/LogarithmPlotter/util/debug.py +++ b/runtime-pyside6/LogarithmPlotter/util/debug.py @@ -22,9 +22,9 @@ from os import path from re import compile CURRENT_PATH = path.dirname(path.realpath(__file__)) -SOURCEMAP_PATH = path.realpath(f"{CURRENT_PATH}/../qml/eu/ad5001/LogarithmPlotter/js/index.mjs.map") +SOURCEMAP_PATH = path.realpath(f"{CURRENT_PATH}/../qml/eu/ad5001/LogarithmPlotter/Common/index.mjs.map") SOURCEMAP_INDEX = None -INDEX_REG = compile(r"build\/runtime-pyside6\/LogarithmPlotter\/qml\/eu\/ad5001\/LogarithmPlotter\/js\/index.mjs:(\d+)") +INDEX_REG = compile(r"build\/runtime-pyside6\/LogarithmPlotter\/qml\/eu\/ad5001\/LogarithmPlotter\/Common\/index.mjs:(\d+)") class LOG_COLORS: diff --git a/runtime-pyside6/LogarithmPlotter/util/latex.py b/runtime-pyside6/LogarithmPlotter/util/latex.py index 78c9d44..570e083 100644 --- a/runtime-pyside6/LogarithmPlotter/util/latex.py +++ b/runtime-pyside6/LogarithmPlotter/util/latex.py @@ -91,7 +91,7 @@ class Latex(QObject): @Property(bool) def supportsAsyncRender(self) -> bool: - return config.getSetting("enable_latex_async") + return config.getSetting("enable_latex_threaded") @Slot(result=bool) def checkLatexInstallation(self) -> bool: