Refactor EventContent
Use proper layouts and get rid of multiple annoyances/bugs like text-wrapping randomly happening in the middle of words, background rectangles being too large, a certain binding loop, etc
This commit is contained in:
parent
4c4d747ecf
commit
d20ab5a348
15
TODO.md
15
TODO.md
|
@ -6,8 +6,8 @@
|
||||||
- Bottom/top bar
|
- Bottom/top bar
|
||||||
- Uploading (+local echo)
|
- Uploading (+local echo)
|
||||||
- Deduplicate uploads
|
- Deduplicate uploads
|
||||||
- Files, links, video, audio
|
- EventLink
|
||||||
- File thumbnails, ask matrix API?
|
- File thumbnails + ask matrix API?
|
||||||
- Encrypted media
|
- Encrypted media
|
||||||
- Loading animation
|
- Loading animation
|
||||||
- GIF thumbnails: load the real animated image
|
- GIF thumbnails: load the real animated image
|
||||||
|
@ -19,8 +19,13 @@
|
||||||
- Video: missing buttons and small size problems
|
- Video: missing buttons and small size problems
|
||||||
- Audio: online playback is buggy, must download+play file
|
- Audio: online playback is buggy, must download+play file
|
||||||
|
|
||||||
- Refactor EventContent
|
- With this as eventText: `https://0x0.st/ztXe.png`, shrinking the window
|
||||||
- No background/padding around medias
|
near its minimum size (seen at 262px) makes the image preview
|
||||||
|
left padding wrong
|
||||||
|
- In the "Leave me" room, "join > Hi > left" aren't combined
|
||||||
|
- GIF glitchy border
|
||||||
|
- Combined pure media events time looks bad
|
||||||
|
- Avatars shouldn't be vertically centered
|
||||||
|
|
||||||
- Copy to X11 selection with new CppUtils class
|
- Copy to X11 selection with new CppUtils class
|
||||||
|
|
||||||
|
@ -41,7 +46,7 @@
|
||||||
- When qml syntax highlighting supports ES6 string interpolation, use that
|
- When qml syntax highlighting supports ES6 string interpolation, use that
|
||||||
|
|
||||||
- Fixes
|
- Fixes
|
||||||
- GIF glitchy border
|
- Event delegates changing height don't scroll the list
|
||||||
- When selecting text and scrolling up, selection stops working after a while
|
- When selecting text and scrolling up, selection stops working after a while
|
||||||
- Ensure all the text that should be copied is copied
|
- Ensure all the text that should be copied is copied
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,5 @@ def guess_mime(file: IO) -> Optional[str]:
|
||||||
|
|
||||||
def plain2html(text: str) -> str:
|
def plain2html(text: str) -> str:
|
||||||
return html.escape(text)\
|
return html.escape(text)\
|
||||||
.replace(" ", " ")\
|
|
||||||
.replace("\n", "<br>")\
|
.replace("\n", "<br>")\
|
||||||
.replace("\t", " " * 4)
|
.replace("\t", " " * 4)
|
||||||
|
|
|
@ -9,7 +9,19 @@ Repeater {
|
||||||
let total = 0
|
let total = 0
|
||||||
|
|
||||||
for (let i = 0; i < repeater.count; i++) {
|
for (let i = 0; i < repeater.count; i++) {
|
||||||
total += repeater.itemAt(i).implicitWidth
|
let item = repeater.itemAt(i)
|
||||||
|
if (item && item.width) total += item.width
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property int childrenWidth: {
|
||||||
|
let total = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < repeater.count; i++) {
|
||||||
|
let item = repeater.itemAt(i)
|
||||||
|
if (item && item.width) total += item.width
|
||||||
}
|
}
|
||||||
|
|
||||||
return total
|
return total
|
||||||
|
|
|
@ -7,10 +7,6 @@ import "../../utils.js" as Utils
|
||||||
|
|
||||||
AudioPlayer {
|
AudioPlayer {
|
||||||
id: audio
|
id: audio
|
||||||
width: Math.min(
|
|
||||||
mainColumn.width - eventContent.spacing * 2,
|
|
||||||
theme.chat.message.audioWidth,
|
|
||||||
)
|
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
onHoveredChanged:
|
onHoveredChanged:
|
||||||
|
|
|
@ -3,125 +3,100 @@ import QtQuick.Layouts 1.12
|
||||||
import "../../Base"
|
import "../../Base"
|
||||||
import "../../utils.js" as Utils
|
import "../../utils.js" as Utils
|
||||||
|
|
||||||
Row {
|
HRowLayout {
|
||||||
id: eventContent
|
id: eventContent
|
||||||
spacing: theme.spacing / 1.25
|
spacing: theme.spacing / 1.25
|
||||||
|
layoutDirection: onRight ? Qt.RightToLeft: Qt.LeftToRight
|
||||||
|
|
||||||
|
|
||||||
readonly property string eventText: Utils.processedEventText(model)
|
readonly property string eventText: Utils.processedEventText(model)
|
||||||
readonly property string eventTime: Utils.formatTime(model.date, false)
|
readonly property string eventTime: Utils.formatTime(model.date, false)
|
||||||
|
readonly property bool pureMedia: ! eventText && previewLinksRepeater.count
|
||||||
|
|
||||||
readonly property string hoveredLink:
|
readonly property string hoveredLink: contentLabel.hoveredLink
|
||||||
nameLabel.hoveredLink || contentLabel.hoveredLink
|
readonly property bool hoveredSelectable: contentHover.hovered
|
||||||
|
|
||||||
readonly property bool hoveredSelectable:
|
readonly property int messageBodyWidth:
|
||||||
nameHover.hovered || contentHover.hovered
|
width - (avatarWrapper.visible ? avatarWrapper.width : 0) -
|
||||||
|
totalSpacing
|
||||||
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: hideAvatar ? 0 : 58
|
id: avatarWrapper
|
||||||
height: hideAvatar ? 0 : collapseAvatar ? 1 : smallAvatar ? 28 : 58
|
opacity: collapseAvatar ? 0 : 1
|
||||||
opacity: hideAvatar || collapseAvatar ? 0 : 1
|
visible: ! hideAvatar
|
||||||
visible: width > 0
|
|
||||||
|
Layout.minimumWidth: 58
|
||||||
|
Layout.minimumHeight: collapseAvatar ? 1 : smallAvatar ? 28 : 58
|
||||||
|
Layout.maximumWidth: Layout.minimumWidth
|
||||||
|
Layout.maximumHeight: Layout.minimumHeight
|
||||||
|
|
||||||
HUserAvatar {
|
HUserAvatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
userId: model.sender_id
|
userId: model.sender_id
|
||||||
displayName: model.sender_name
|
displayName: model.sender_name
|
||||||
avatarUrl: model.sender_avatar
|
avatarUrl: model.sender_avatar
|
||||||
width: hideAvatar ? 0 : 58
|
|
||||||
height: hideAvatar ? 0 : collapseAvatar ? 1 : 58
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: isOwn?
|
|
||||||
theme.chat.message.ownBackground :
|
|
||||||
theme.chat.message.background
|
|
||||||
|
|
||||||
//width: nameLabel.implicitWidth
|
|
||||||
width: Math.min(
|
|
||||||
eventList.width - avatar.width - eventContent.spacing,
|
|
||||||
theme.fontSize.normal * 0.5 * 75, // 600 with 16px font
|
|
||||||
|
|
||||||
Math.max(
|
|
||||||
nameLabel.visible ? (nameLabel.implicitWidth + 1) : 0,
|
|
||||||
|
|
||||||
contentLabel.implicitWidth + 1,
|
|
||||||
|
|
||||||
previewLinksRepeater.count > 0 ?
|
|
||||||
theme.chat.message.thumbnailWidth : 0,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
height: childrenRect.height
|
|
||||||
y: parent.height / 2 - height / 2
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: mainColumn
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: theme.spacing / 1.75
|
height: collapseAvatar ? 1 : 58
|
||||||
topPadding: theme.spacing / 1.75
|
}
|
||||||
bottomPadding: topPadding
|
|
||||||
|
|
||||||
HSelectableLabel {
|
|
||||||
id: nameLabel
|
|
||||||
width: parent.width
|
|
||||||
visible: ! hideNameLine
|
|
||||||
container: selectableLabelContainer
|
|
||||||
selectable: ! unselectableNameLine
|
|
||||||
leftPadding: eventContent.spacing
|
|
||||||
rightPadding: leftPadding
|
|
||||||
|
|
||||||
// This is +0.1 and content is +0 instead of the opposite,
|
|
||||||
// because the eventList is reversed
|
|
||||||
index: model.index + 0.1
|
|
||||||
|
|
||||||
text: Utils.coloredNameHtml(model.sender_name, model.sender_id)
|
|
||||||
textFormat: Text.RichText
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
// horizontalAlignment: onRight ? Text.AlignRight : Text.AlignLeft
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
|
||||||
|
|
||||||
function selectAllText() {
|
|
||||||
// select the sender name, body and date
|
|
||||||
container.clearSelection()
|
|
||||||
nameLabel.selectAll()
|
|
||||||
contentLabel.selectAll()
|
|
||||||
contentLabel.updateContainerSelectedTexts()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler { id: nameHover }
|
HColumnLayout {
|
||||||
}
|
id: contentColumn
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
HSelectableLabel {
|
HSelectableLabel {
|
||||||
id: contentLabel
|
id: contentLabel
|
||||||
visible: Boolean(eventContent.eventText)
|
|
||||||
width: parent.width
|
|
||||||
container: selectableLabelContainer
|
container: selectableLabelContainer
|
||||||
index: model.index
|
index: model.index
|
||||||
|
|
||||||
|
topPadding: theme.spacing / 1.75
|
||||||
|
bottomPadding: topPadding
|
||||||
leftPadding: eventContent.spacing
|
leftPadding: eventContent.spacing
|
||||||
rightPadding: leftPadding
|
rightPadding: leftPadding
|
||||||
bottomPadding: previewLinksRepeater.count > 0 ?
|
|
||||||
mainColumn.bottomPadding : 0
|
|
||||||
|
|
||||||
text: theme.chat.message.styleInclude +
|
color: theme.chat.message.body
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
textFormat: Text.RichText
|
||||||
|
text:
|
||||||
|
// CSS
|
||||||
|
theme.chat.message.styleInclude +
|
||||||
|
|
||||||
|
// Sender name
|
||||||
|
(hideNameLine ? "" : (
|
||||||
|
"<div class='sender'>" +
|
||||||
|
Utils.coloredNameHtml(model.sender_name, model.sender_id) +
|
||||||
|
"</div>")) +
|
||||||
|
|
||||||
|
// Message body
|
||||||
eventContent.eventText +
|
eventContent.eventText +
|
||||||
// time
|
|
||||||
// for some reason, if there's only one space,
|
// Time
|
||||||
|
// For some reason, if there's only one space,
|
||||||
// times will be on their own lines most of the time.
|
// times will be on their own lines most of the time.
|
||||||
" " +
|
" " +
|
||||||
"<font size=" + theme.fontSize.small +
|
"<font size=" + theme.fontSize.small +
|
||||||
"px color=" + theme.chat.message.date + '>' +
|
"px color=" + theme.chat.message.date + '>' +
|
||||||
eventTime +
|
eventTime +
|
||||||
"</font>" +
|
"</font>" +
|
||||||
// local echo icon
|
|
||||||
|
// Local echo icon
|
||||||
(model.is_local_echo ?
|
(model.is_local_echo ?
|
||||||
" <font size=" + theme.fontSize.small +
|
" <font size=" + theme.fontSize.small +
|
||||||
"px>⏳</font>" : "")
|
"px>⏳</font>" : "")
|
||||||
|
|
||||||
color: theme.chat.message.body
|
transform: Translate {
|
||||||
wrapMode: Text.Wrap
|
x: onRight ?
|
||||||
textFormat: Text.RichText
|
contentLabel.width - contentLabel.paintedWidth -
|
||||||
|
contentLabel.leftPadding - contentLabel.rightPadding :
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.maximumWidth: Math.min(
|
||||||
|
// 600px with 16px font
|
||||||
|
theme.fontSize.normal * 0.5 * 75,
|
||||||
|
messageBodyWidth - leftPadding - rightPadding,
|
||||||
|
)
|
||||||
|
|
||||||
function selectAllText() {
|
function selectAllText() {
|
||||||
// Select the message body without the date or name
|
// Select the message body without the date or name
|
||||||
|
@ -135,17 +110,66 @@ Row {
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler { id: contentHover }
|
HoverHandler { id: contentHover }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(
|
||||||
|
parent.paintedWidth +
|
||||||
|
parent.leftPadding + parent.rightPadding,
|
||||||
|
|
||||||
|
previewLinksRepeater.childrenWidth +
|
||||||
|
(pureMedia ? 0 : parent.leftPadding + parent.rightPadding),
|
||||||
|
)
|
||||||
|
height: contentColumn.height
|
||||||
|
z: -1
|
||||||
|
color: isOwn?
|
||||||
|
theme.chat.message.ownBackground :
|
||||||
|
theme.chat.message.background
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
HRepeater {
|
||||||
id: previewLinksRepeater
|
id: previewLinksRepeater
|
||||||
model: eventDelegate.currentItem.links
|
model: eventDelegate.currentItem.links
|
||||||
|
|
||||||
EventMediaLoader {
|
EventMediaLoader {
|
||||||
info: eventDelegate.currentItem
|
info: eventDelegate.currentItem
|
||||||
mediaUrl: modelData
|
mediaUrl: modelData
|
||||||
|
|
||||||
|
Layout.bottomMargin: contentLabel.bottomPadding * multiply
|
||||||
|
Layout.leftMargin: contentLabel.leftPadding * multiply
|
||||||
|
Layout.rightMargin: contentLabel.rightPadding * multiply
|
||||||
|
|
||||||
|
Layout.minimumWidth:
|
||||||
|
type === EventDelegate.Media.File ?
|
||||||
|
theme.chat.message.fileMinWidth : -1
|
||||||
|
|
||||||
|
Layout.preferredWidth:
|
||||||
|
type === EventDelegate.Media.Image ?
|
||||||
|
(item ? item.fitSize.width : 0) :
|
||||||
|
|
||||||
|
type === EventDelegate.Media.Video ?
|
||||||
|
theme.chat.message.videoWidth :
|
||||||
|
|
||||||
|
type === EventDelegate.Media.Audio ?
|
||||||
|
theme.chat.message.audioWidth :
|
||||||
|
|
||||||
|
-1
|
||||||
|
|
||||||
|
Layout.maximumWidth:
|
||||||
|
messageBodyWidth - Layout.leftMargin - Layout.rightMargin
|
||||||
|
|
||||||
|
Layout.maximumHeight:
|
||||||
|
type === EventDelegate.Media.Image && item ?
|
||||||
|
Utils.fitSize(
|
||||||
|
Layout.maximumWidth,
|
||||||
|
item.fitSize.height,
|
||||||
|
Layout.maximumWidth
|
||||||
|
).height : -1
|
||||||
|
|
||||||
|
readonly property int multiply: pureMedia ? 0 : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
HSpacer {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,6 @@ Column {
|
||||||
onRight ||
|
onRight ||
|
||||||
combine
|
combine
|
||||||
|
|
||||||
readonly property bool unselectableNameLine:
|
|
||||||
hideNameLine && ! (onRight && ! combine)
|
|
||||||
|
|
||||||
readonly property int cursorShape:
|
readonly property int cursorShape:
|
||||||
eventContent.hoveredLink || hoveredMediaTypeUrl.length > 0 ?
|
eventContent.hoveredLink || hoveredMediaTypeUrl.length > 0 ?
|
||||||
Qt.PointingHandCursor :
|
Qt.PointingHandCursor :
|
||||||
|
@ -96,7 +93,7 @@ Column {
|
||||||
|
|
||||||
EventContent {
|
EventContent {
|
||||||
id: eventContent
|
id: eventContent
|
||||||
x: onRight ? parent.width - width : 0
|
width: parent.width
|
||||||
|
|
||||||
Behavior on x { HNumberAnimation {} }
|
Behavior on x { HNumberAnimation {} }
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,6 @@ import QtQuick.Layouts 1.12
|
||||||
import "../../Base"
|
import "../../Base"
|
||||||
|
|
||||||
HTile {
|
HTile {
|
||||||
width: Math.min(
|
|
||||||
mainColumn.width - eventContent.spacing * 2,
|
|
||||||
theme.chat.message.thumbnailWidth,
|
|
||||||
)
|
|
||||||
|
|
||||||
onLeftClicked: Qt.openUrlExternally(fileUrl)
|
onLeftClicked: Qt.openUrlExternally(fileUrl)
|
||||||
onRightClicked: eventDelegate.openContextMenu()
|
onRightClicked: eventDelegate.openContextMenu()
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import "../../Base"
|
import "../../Base"
|
||||||
|
import "../../utils.js" as Utils
|
||||||
|
|
||||||
HImage {
|
HImage {
|
||||||
id: image
|
id: image
|
||||||
sourceSize.width: theme.chat.message.thumbnailWidth
|
sourceSize.width: theme.chat.message.thumbnailWidth
|
||||||
sourceSize.height: theme.chat.message.thumbnailWidth
|
sourceSize.height: theme.chat.message.thumbnailWidth
|
||||||
width: Math.min(
|
width: fitSize.width
|
||||||
mainColumn.width - eventContent.spacing * 2,
|
height: fitSize.height
|
||||||
implicitWidth,
|
|
||||||
theme.chat.message.thumbnailWidth,
|
// Leaving PreserveAspectFit creates a binding loop, and is uneeded
|
||||||
)
|
// since we calculate ourself the right size.
|
||||||
|
fillMode: Image.Pad
|
||||||
|
|
||||||
|
|
||||||
// source = thumbnail, fullSource = full original image
|
// source = thumbnail, fullSource = full original image
|
||||||
property url fullSource: source
|
property url fullSource: source
|
||||||
|
|
||||||
|
readonly property size fitSize: Utils.fitSize(
|
||||||
|
implicitWidth, implicitHeight, theme.chat.message.thumbnailWidth,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: if (! image.animated) Qt.openUrlExternally(fullSource)
|
onTapped: if (! image.animated) Qt.openUrlExternally(fullSource)
|
||||||
|
|
|
@ -7,10 +7,6 @@ import "../../utils.js" as Utils
|
||||||
|
|
||||||
VideoPlayer {
|
VideoPlayer {
|
||||||
id: video
|
id: video
|
||||||
width: fullScreen ? implicitWidth : Math.min(
|
|
||||||
mainColumn.width - eventContent.spacing * 2,
|
|
||||||
theme.chat.message.videoWidth,
|
|
||||||
)
|
|
||||||
|
|
||||||
onHoveredChanged:
|
onHoveredChanged:
|
||||||
eventDelegate.hoveredMediaTypeUrl =
|
eventDelegate.hoveredMediaTypeUrl =
|
||||||
|
|
|
@ -167,6 +167,16 @@ function thumbnailParametersFor(width, height) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function fitSize(width, height, max) {
|
||||||
|
if (width >= height) {
|
||||||
|
let new_width = Math.min(width, max)
|
||||||
|
return Qt.size(new_width, height / (width / new_width))
|
||||||
|
}
|
||||||
|
let new_height = Math.min(height, max)
|
||||||
|
return Qt.size(width / (height / new_height), new_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function minutesBetween(date1, date2) {
|
function minutesBetween(date1, date2) {
|
||||||
return ((date2 - date1) / 1000) / 60
|
return ((date2 - date1) / 1000) / 60
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,6 +315,7 @@ chat:
|
||||||
string styleSheet:
|
string styleSheet:
|
||||||
"* { white-space: pre-wrap }" +
|
"* { white-space: pre-wrap }" +
|
||||||
"a { color: " + link + " }" +
|
"a { color: " + link + " }" +
|
||||||
|
"p { margin-top: 0 }" +
|
||||||
|
|
||||||
"code { font-family: " + fontFamily.mono + "; " +
|
"code { font-family: " + fontFamily.mono + "; " +
|
||||||
"color: " + code + " }" +
|
"color: " + code + " }" +
|
||||||
|
@ -327,14 +328,17 @@ chat:
|
||||||
"h5 { font-size: " + fontSize.small + "px }" +
|
"h5 { font-size: " + fontSize.small + "px }" +
|
||||||
"h6 { font-size: " + fontSize.smaller + "px }" +
|
"h6 { font-size: " + fontSize.smaller + "px }" +
|
||||||
|
|
||||||
|
".sender { margin-bottom: " + spacing / 2 + " }" +
|
||||||
".quote { color: " + quote + " }"
|
".quote { color: " + quote + " }"
|
||||||
|
|
||||||
string styleInclude:
|
string styleInclude:
|
||||||
'<style type"text/css">\n' + styleSheet + '\n</style>\n'
|
'<style type"text/css">\n' + styleSheet + '\n</style>\n'
|
||||||
|
|
||||||
|
// TODO rename
|
||||||
|
int fileMinWidth: 256
|
||||||
int thumbnailWidth: 256
|
int thumbnailWidth: 256
|
||||||
int videoWidth: 512
|
int videoWidth: 640
|
||||||
int audioWidth: 512
|
int audioWidth: 320
|
||||||
|
|
||||||
daybreak:
|
daybreak:
|
||||||
color background: colors.strongBackground
|
color background: colors.strongBackground
|
||||||
|
|
Loading…
Reference in New Issue
Block a user