From d2d6fac778fea88294c94505fe1c3e7cef7822dd Mon Sep 17 00:00:00 2001 From: Slava Monich Date: Thu, 24 Dec 2020 05:42:41 +0200 Subject: [PATCH] Fixed Emoji image positioning in multiline StyledText Combination of maximumLineCount and TruncationMode.Elide (or Fade) breaks Emoji image alignment, pushing the image down. Explicitly truncating the text fixes the problem, at expense of certain runtime overhead. Also, toggle full and truncated Web page preview on tap. --- harbour-fernschreiber.pro | 1 + qml/components/MessageListViewItem.qml | 11 +-- qml/components/MultilineEmojiLabel.qml | 78 +++++++++++++++++++++ qml/components/WebPagePreview.qml | 97 +++++++++++--------------- 4 files changed, 121 insertions(+), 66 deletions(-) create mode 100644 qml/components/MultilineEmojiLabel.qml diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro index ac8118d..417c776 100644 --- a/harbour-fernschreiber.pro +++ b/harbour-fernschreiber.pro @@ -51,6 +51,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \ qml/components/MessageListViewItem.qml \ qml/components/MessageListViewItemSimple.qml \ qml/components/MessageOverlayFlickable.qml \ + qml/components/MultilineEmojiLabel.qml \ qml/components/PinnedMessageItem.qml \ qml/components/PollPreview.qml \ qml/components/PressEffect.qml \ diff --git a/qml/components/MessageListViewItem.qml b/qml/components/MessageListViewItem.qml index dfc05d0..a392514 100644 --- a/qml/components/MessageListViewItem.qml +++ b/qml/components/MessageListViewItem.qml @@ -57,6 +57,8 @@ ListItem { mouseX < (extraContentLoader.x + extraContentLoader.width) && mouseY < (extraContentLoader.y + extraContentLoader.height)) { extraContent.clicked() + } else if (webPagePreviewLoader.item) { + webPagePreviewLoader.item.clicked() } } } @@ -397,17 +399,10 @@ ListItem { active: false asynchronous: true width: parent.width - height: typeof myMessage.content.web_page !== "undefined" ? precalculatedValues.webPagePreviewHeight : 0 + height: (status === Loader.Ready) ? item.implicitHeight : myMessage.content.web_page ? precalculatedValues.webPagePreviewHeight : 0 sourceComponent: Component { - id: webPagePreviewComponent WebPagePreview { - id: webPagePreview - - onImplicitHeightChanged: { - webPagePreviewLoader.height = webPagePreview.implicitHeight; - } - webPageData: myMessage.content.web_page width: parent.width highlighted: messageListItem.highlighted diff --git a/qml/components/MultilineEmojiLabel.qml b/qml/components/MultilineEmojiLabel.qml new file mode 100644 index 0000000..0199af7 --- /dev/null +++ b/qml/components/MultilineEmojiLabel.qml @@ -0,0 +1,78 @@ +/* + Copyright (C) 2020 Slava Monich and other contributors + + This file is part of Fernschreiber. + + Fernschreiber is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fernschreiber is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Fernschreiber. If not, see . +*/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../js/twemoji.js" as Emoji + +// Combination of maximumLineCount and TruncationMode.Elide (or Fade) breaks +// Emoji image alignment, pushing the image down. This one aligns the image +// correctly on its line. +Label { + property string rawText + property int maxLineCount + + wrapMode: Text.Wrap + textFormat: Text.StyledText + truncationMode: TruncationMode.Elide + + // lineCount is unreliable for StyledText with images and line breaks + readonly property int fontSize: font.pixelSize + readonly property int actualLineHeight: (text === rawText) ? fontSize : (fontSize * 6 / 5) + readonly property int actualLineCount: Math.floor(implicitHeight/actualLineHeight) + + Component.onCompleted: refitText() + onFontSizeChanged: refitText() + onWidthChanged: refitText() + onRawTextChanged: refitText() + onMaxLineCountChanged: refitText() + + function emojify(str) { + return Emoji.emojify(str, fontSize) + } + + function refitText() { + text = emojify(rawText) + if (maxLineCount > 0) { + var divisor = 1 + var max = rawText.length + var min = max + while (actualLineCount > maxLineCount && divisor < rawText.length) { + max = min + divisor++ + min = rawText.length/divisor + text = emojify(rawText.substr(0, min) + "…") + } + while (min < max) { + var mid = Math.floor((min + max)/2) + if (mid === min) { + text = emojify(rawText.substr(0, min) + "…") + break + } else { + text = emojify(rawText.substr(0, mid) + "…") + if (actualLineCount > maxLineCount) { + max = mid + } else { + min = mid + } + } + } + } + } +} diff --git a/qml/components/WebPagePreview.qml b/qml/components/WebPagePreview.qml index a9cfa12..efd4ced 100644 --- a/qml/components/WebPagePreview.qml +++ b/qml/components/WebPagePreview.qml @@ -19,101 +19,86 @@ import QtQuick 2.6 import QtGraphicalEffects 1.0 import Sailfish.Silica 1.0 -import "../components" -import "../js/twemoji.js" as Emoji +import WerkWolf.Fernschreiber 1.0 import "../js/functions.js" as Functions Column { - id: webPagePreviewColumn property var webPageData; - property var pictureFileInformation; - property bool hasImage: false; property bool largerFontSize: false; property bool highlighted + readonly property bool hasImage: picture.fileId !== 0 + readonly property int fontSize: largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall spacing: Theme.paddingSmall Component.onCompleted: { - updateWebPage(); - } - - function updateWebPage() { if (webPageData) { - if (typeof webPageData.photo !== "undefined") { - hasImage = true; + if (webPageData.photo) { // Check first which size fits best... + var photo for (var i = 0; i < webPageData.photo.sizes.length; i++) { - pictureFileInformation = webPageData.photo.sizes[i].photo; + photo = webPageData.photo.sizes[i].photo; if (webPageData.photo.sizes[i].width >= webPagePreviewColumn.width) { break; } } - if (pictureFileInformation.local.is_downloading_completed) { - singleImage.source = pictureFileInformation.local.path; - } else { - tdLibWrapper.downloadFile(pictureFileInformation.id); + if (photo) { + picture.fileInformation = photo } } } } - Connections { - target: tdLibWrapper - onFileUpdated: { - if (typeof pictureFileInformation !== "undefined" && fileId === pictureFileInformation.id) { - if (fileInformation.local.is_downloading_completed) { - pictureFileInformation = fileInformation; - singleImage.source = fileInformation.local.path; - } - } - } + function clicked() { + descriptionText.toggleMaxLineCount() } - Label { + TDLibFile { + id: picture + tdlib: tdLibWrapper + autoLoad: true + } + + MultilineEmojiLabel { id: siteNameText width: parent.width - text: webPageData.site_name ? Emoji.emojify(webPageData.site_name, font.pixelSize) : "" - font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall + rawText: webPageData.site_name ? webPageData.site_name : "" + font.pixelSize: webPagePreviewColumn.fontSize font.bold: true color: Theme.secondaryHighlightColor - truncationMode: TruncationMode.Fade - maximumLineCount: 1 - textFormat: Text.StyledText - visible: (text !== "") + visible: (rawText !== "") + maxLineCount: 1 } - Label { + MultilineEmojiLabel { id: titleText width: parent.width - text: webPageData.title ? Emoji.emojify(webPageData.title, font.pixelSize) : "" - font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall + rawText: webPageData.title ? webPageData.title : "" + font.pixelSize: webPagePreviewColumn.fontSize font.bold: true - truncationMode: TruncationMode.Fade - wrapMode: Text.Wrap - maximumLineCount: 2 - textFormat: Text.StyledText - visible: (text !== "") + visible: (rawText !== "") + maxLineCount: 2 } - Label { + MultilineEmojiLabel { id: descriptionText width: parent.width - text: webPageData.description ? Emoji.emojify(Functions.enhanceMessageText(webPageData.description), font.pixelSize) : "" - font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall - truncationMode: TruncationMode.Fade - wrapMode: Text.Wrap - maximumLineCount: 3 - textFormat: Text.StyledText - visible: (text !== "") - linkColor: Theme.highlightColor + rawText: webPageData.description ? Functions.enhanceMessageText(webPageData.description) : "" + font.pixelSize: webPagePreviewColumn.fontSize + visible: (rawText !== "") + readonly property int defaultMaxLineCount: 3 + maxLineCount: defaultMaxLineCount onLinkActivated: { Functions.handleLink(link); } + function toggleMaxLineCount() { + maxLineCount = maxLineCount > 0 ? 0 : defaultMaxLineCount + } } Item { @@ -133,15 +118,16 @@ Column { fillMode: Image.PreserveAspectCrop autoTransform: true asynchronous: true - visible: hasImage && status === Image.Ready + source: picture.isDownloadingCompleted ? picture.path : "" + visible: opacity > 0 opacity: hasImage && status === Image.Ready ? 1 : 0 layer.enabled: webPagePreviewColumn.highlighted layer.effect: PressEffect { source: singleImage } - Behavior on opacity { NumberAnimation {} } + Behavior on opacity { FadeAnimation {} } MouseArea { anchors.fill: parent onClicked: { - pageStack.push(Qt.resolvedUrl("../pages/ImagePage.qml"), { "photoData" : webPageData.photo, "pictureFileInformation" : pictureFileInformation }); + pageStack.push(Qt.resolvedUrl("../pages/ImagePage.qml"), { "photoData" : webPageData.photo, "pictureFileInformation" : picture.fileInformation }); } } } @@ -154,17 +140,12 @@ Column { } Label { - id: noPreviewAvailableText - width: parent.width text: qsTr("Preview not supported for this link...") font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeExtraSmall : Theme.fontSizeTiny font.italic: true color: Theme.secondaryColor truncationMode: TruncationMode.Fade - wrapMode: Text.Wrap - maximumLineCount: 1 - textFormat: Text.StyledText visible: !siteNameText.visible && !titleText.visible && !descriptionText.visible && !webPagePreviewImageItem.visible }