fix(qt): featured artist names in track rows are plain spans, not underlined links
StyledText underlines every `<a>` anchor unconditionally and nothing else in the app underlines a link. The names are now colour spans in the same StyledText (elision kept), and the click and hover hit test measures the prefix up to each name with the row's own font, so each name still opens its page or its search on its own. cc
This commit is contained in:
1 parent
34ea883d07
commit
64d75e497a
1 file changed
+69
-21
@@ -990,13 +990,28 @@ Rectangle {
|
|||||||
// Main artist first, then the featured performers, one comma
|
// Main artist first, then the featured performers, one comma
|
||||||
// separated list. Local / media-server rows carry no
|
// separated list. Local / media-server rows carry no
|
||||||
// `featured` and stay as they are. With links enabled and
|
// `featured` and stay as they are. With links enabled and
|
||||||
// featured names present, every name is its own StyledText
|
// featured names present, every name is its own clickable
|
||||||
// link: the main artist opens its page as before; a featured
|
// SPAN: the main artist opens its page as before; a featured
|
||||||
// name opens its page when the album credits gave it an id
|
// name opens its page when the album credits gave it an id
|
||||||
// and the search for that name otherwise. StyledText elides
|
// and the search for that name otherwise. Spans, not `<a>`
|
||||||
// like plain text (probed: truncated stays true on one line).
|
// anchors: StyledText underlines every anchor unconditionally
|
||||||
|
// (qquickstyledtext.cpp, setFontUnderline(true)) and nothing
|
||||||
|
// else in the app underlines a link, so the hit test is done
|
||||||
|
// by measuring the prefix up to each name with the same font
|
||||||
|
// (TextMetrics below). StyledText still elides like plain
|
||||||
|
// text (probed: truncated stays true on one line).
|
||||||
readonly property var featured: root.showFeaturedArtists ? (root.item.featured || []) : []
|
readonly property var featured: root.showFeaturedArtists ? (root.item.featured || []) : []
|
||||||
readonly property bool linkMode: root.artistLink && featured.length > 0
|
readonly property bool linkMode: root.artistLink && featured.length > 0
|
||||||
|
/// The names as displayed, in order: {name, href} — href ""
|
||||||
|
/// = the main artist with no page to open.
|
||||||
|
readonly property var spans: {
|
||||||
|
var out = [{ "name": String(root.item.artist || ""), "href": root.item.artistId ? "main" : "" }]
|
||||||
|
for (var i = 0; i < featured.length; i++)
|
||||||
|
out.push({ "name": String(featured[i].name || ""), "href": "f:" + i })
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
/// The span under the pointer (-1 = none).
|
||||||
|
property int hotSpan: -1
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
||||||
}
|
}
|
||||||
@@ -1005,51 +1020,84 @@ Rectangle {
|
|||||||
return ("0" + Math.round(v * 255).toString(16)).slice(-2)
|
return ("0" + Math.round(v * 255).toString(16)).slice(-2)
|
||||||
}).join("")
|
}).join("")
|
||||||
}
|
}
|
||||||
function link(href, name) {
|
function span(i) {
|
||||||
var hot = artistLine.hoveredLink === href
|
var s = artistLine.spans[i]
|
||||||
return '<a href="' + href + '"><font color="'
|
var hot = artistLine.hotSpan === i && s.href !== ""
|
||||||
+ hex(hot ? theme.textPrimary : theme.textMuted) + '">' + esc(name) + '</font></a>'
|
return '<font color="' + hex(hot ? theme.textPrimary : theme.textMuted) + '">' + esc(s.name) + '</font>'
|
||||||
}
|
}
|
||||||
textFormat: linkMode ? Text.StyledText : Text.PlainText
|
textFormat: linkMode ? Text.StyledText : Text.PlainText
|
||||||
text: {
|
text: {
|
||||||
var names = featured.map(function (f) { return f.name })
|
var names = featured.map(function (f) { return f.name })
|
||||||
if (!linkMode)
|
if (!linkMode)
|
||||||
return (root.item.artist || "") + (names.length ? ", " + names.join(", ") : "")
|
return (root.item.artist || "") + (names.length ? ", " + names.join(", ") : "")
|
||||||
var parts = [root.item.artistId ? link("main", root.item.artist) : esc(root.item.artist)]
|
var parts = []
|
||||||
for (var i = 0; i < featured.length; i++)
|
for (var i = 0; i < spans.length; i++)
|
||||||
parts.push(link("f:" + i, featured[i].name))
|
parts.push(span(i))
|
||||||
return parts.join(", ")
|
return parts.join(", ")
|
||||||
}
|
}
|
||||||
linkColor: theme.textMuted
|
|
||||||
color: root.artistLink && root.item.artistId && artistLinkArea.containsMouse
|
color: root.artistLink && root.item.artistId && artistLinkArea.containsMouse
|
||||||
? theme.textPrimary : theme.textMuted
|
? theme.textPrimary : theme.textMuted
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
onLinkActivated: function (href) {
|
/// Which span sits at `x` (-1 = a separator or past the text):
|
||||||
if (href === "main") {
|
/// the prefix up to each name's end is measured with this
|
||||||
|
/// Text's own font, so the edges match what is drawn.
|
||||||
|
function spanAt(x) {
|
||||||
|
if (!linkMode || x < 0)
|
||||||
|
return -1
|
||||||
|
var prefix = ""
|
||||||
|
for (var i = 0; i < spans.length; i++) {
|
||||||
|
spanMetrics.text = prefix
|
||||||
|
var from = spanMetrics.advanceWidth
|
||||||
|
prefix += spans[i].name
|
||||||
|
spanMetrics.text = prefix
|
||||||
|
if (x >= from && x < spanMetrics.advanceWidth)
|
||||||
|
return i
|
||||||
|
prefix += ", "
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
function activate(i) {
|
||||||
|
var s = artistLine.spans[i]
|
||||||
|
if (!s || s.href === "")
|
||||||
|
return
|
||||||
|
if (s.href === "main") {
|
||||||
if (root.routeGoToExternally) root.goToRequested("artist")
|
if (root.routeGoToExternally) root.goToRequested("artist")
|
||||||
else QbzArtist.openArtist(root.item.artistId)
|
else QbzArtist.openArtist(root.item.artistId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var f = featured[parseInt(href.slice(2))]
|
var f = featured[parseInt(s.href.slice(2))]
|
||||||
if (!f) return
|
if (!f) return
|
||||||
if (f.id) QbzArtist.openArtist(f.id)
|
if (f.id) QbzArtist.openArtist(f.id)
|
||||||
else QbzSearch.searchSubmit(f.name)
|
else QbzSearch.searchSubmit(f.name)
|
||||||
}
|
}
|
||||||
HoverHandler {
|
TextMetrics {
|
||||||
enabled: artistLine.linkMode
|
id: spanMetrics
|
||||||
cursorShape: artistLine.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
font: artistLine.font
|
||||||
}
|
}
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: artistLinkArea
|
id: artistLinkArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.artistLink && !!root.item.artistId && !artistLine.linkMode
|
enabled: root.artistLink && (artistLine.linkMode || !!root.item.artistId)
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: (artistLine.linkMode
|
||||||
|
? (artistLine.hotSpan >= 0 && artistLine.spans[artistLine.hotSpan].href !== "")
|
||||||
|
: enabled) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
onPositionChanged: function (mouse) {
|
||||||
|
if (artistLine.linkMode)
|
||||||
|
artistLine.hotSpan = artistLine.spanAt(mouse.x)
|
||||||
|
}
|
||||||
|
onExited: artistLine.hotSpan = -1
|
||||||
// Same routing as the menu's "Go to artist" — Slint drives
|
// Same routing as the menu's "Go to artist" — Slint drives
|
||||||
// both through one handler, so gating only the menu would
|
// both through one handler, so gating only the menu would
|
||||||
// leave the LINK navigating to the track's own artist.
|
// leave the LINK navigating to the track's own artist.
|
||||||
onClicked: {
|
onClicked: function (mouse) {
|
||||||
|
if (artistLine.linkMode) {
|
||||||
|
var i = artistLine.spanAt(mouse.x)
|
||||||
|
if (i >= 0)
|
||||||
|
artistLine.activate(i)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (root.routeGoToExternally) root.goToRequested("artist")
|
if (root.routeGoToExternally) root.goToRequested("artist")
|
||||||
else QbzArtist.openArtist(root.item.artistId)
|
else QbzArtist.openArtist(root.item.artistId)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user