1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-30 09:32:05 -04:00

launcher: add elide helpers for RichText

This commit is contained in:
bbedward
2026-04-21 15:18:41 -04:00
parent cf382c0322
commit c6ed64b24e
2 changed files with 229 additions and 107 deletions

View File

@@ -0,0 +1,93 @@
.pragma library
function stripHtmlTags(html) {
if (!html)
return "";
return String(html)
.replace(/<[^>]+>/g, "")
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, "\"")
.replace(/&#039;/g, "'");
}
function elideRichText(html, visibleBudget) {
if (!html)
return "";
if (visibleBudget <= 0)
return "";
var out = "";
var visible = 0;
var i = 0;
var openTags = [];
var len = html.length;
while (i < len && visible < visibleBudget) {
var ch = html.charAt(i);
if (ch === "<") {
var end = html.indexOf(">", i);
if (end < 0)
break;
var tag = html.substring(i, end + 1);
out += tag;
var isClose = tag.charAt(1) === "/";
var match = tag.match(/^<\/?([a-zA-Z]+)/);
var name = match ? match[1] : "";
if (isClose) {
if (openTags.length > 0 && openTags[openTags.length - 1] === name)
openTags.pop();
} else if (!tag.endsWith("/>") && name) {
openTags.push(name);
}
i = end + 1;
} else if (ch === "&") {
var eend = html.indexOf(";", i);
if (eend < 0 || eend - i > 6) {
out += "&amp;";
visible++;
i++;
} else {
out += html.substring(i, eend + 1);
visible++;
i = eend + 1;
}
} else {
out += ch;
visible++;
i++;
}
}
while (i < len && html.charAt(i) === "<") {
var tend = html.indexOf(">", i);
if (tend < 0)
break;
var ttag = html.substring(i, tend + 1);
out += ttag;
var tisClose = ttag.charAt(1) === "/";
var tmatch = ttag.match(/^<\/?([a-zA-Z]+)/);
var tname = tmatch ? tmatch[1] : "";
if (tisClose) {
if (openTags.length > 0 && openTags[openTags.length - 1] === tname)
openTags.pop();
} else if (!ttag.endsWith("/>") && tname) {
openTags.push(tname);
}
i = tend + 1;
}
if (i < len) {
out = out.replace(/\s+$/, "");
while (openTags.length > 0)
out += "</" + openTags.pop() + ">";
out += "…";
} else {
while (openTags.length > 0)
out += "</" + openTags.pop() + ">";
}
return out;
}

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Widgets
import "../../Common/htmlElide.js" as HtmlElide
Rectangle {
id: root
@@ -72,131 +73,159 @@ Rectangle {
}
}
Row {
anchors.fill: parent
AppIconRenderer {
id: iconRenderer
width: 36
height: 36
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
iconValue: root.iconValue
iconSize: 36
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
materialIconSizeAdjustment: 12
}
Item {
id: textColumn
anchors.left: iconRenderer.right
anchors.leftMargin: Theme.spacingM
anchors.right: rightContent.left
anchors.rightMargin: rightContent.width > 0 ? Theme.spacingM : 0
anchors.verticalCenter: parent.verticalCenter
height: nameText.implicitHeight + (subText.visible ? subText.height + 2 : 0)
Text {
id: nameText
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text: root.item?._hName ?? root.item?.name ?? ""
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
font.family: Theme.fontFamily
color: Theme.surfaceText
wrapMode: Text.WordWrap
maximumLineCount: 1
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
}
TextMetrics {
id: subProbe
font.pixelSize: Theme.fontSizeSmall
font.family: Theme.fontFamily
elide: Qt.ElideRight
elideWidth: textColumn.width
text: root.item?._hRich ? HtmlElide.stripHtmlTags(root.item?._hSub ?? "") : ""
}
readonly property int _richBudget: {
if (!subProbe.text)
return 0;
var e = subProbe.elidedText;
return e.endsWith("…") ? e.length - 1 : e.length;
}
Text {
id: subText
anchors.left: parent.left
anchors.right: parent.right
anchors.top: nameText.bottom
anchors.topMargin: 2
text: root.item?._hRich ? HtmlElide.elideRichText(root.item._hSub ?? "", textColumn._richBudget) : (root.item?.subtitle ?? "")
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeSmall
font.family: Theme.fontFamily
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
maximumLineCount: 1
elide: Text.ElideRight
visible: (root.item?.subtitle ?? "").length > 0
horizontalAlignment: Text.AlignLeft
}
}
Row {
id: rightContent
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
AppIconRenderer {
width: 36
height: 36
Rectangle {
id: allModeToggle
visible: root.item?.type === "plugin_browse"
width: 28
height: 28
radius: 14
anchors.verticalCenter: parent.verticalCenter
iconValue: root.iconValue
iconSize: 36
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
materialIconSizeAdjustment: 12
}
color: allModeToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width
spacing: 2
Text {
width: parent.width
text: root.item?._hName ?? root.item?.name ?? ""
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
font.family: Theme.fontFamily
color: Theme.surfaceText
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
property bool isAllowed: {
if (root.item?.type !== "plugin_browse")
return false;
var pluginId = root.item?.data?.pluginId;
if (!pluginId)
return false;
SettingsData.launcherPluginVisibility;
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
}
Text {
width: parent.width
text: root.item?._hSub ?? root.item?.subtitle ?? ""
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeSmall
font.family: Theme.fontFamily
color: Theme.surfaceVariantText
elide: Text.ElideRight
clip: true
visible: (root.item?.subtitle ?? "").length > 0
horizontalAlignment: Text.AlignLeft
DankIcon {
anchors.centerIn: parent
name: allModeToggle.isAllowed ? "visibility" : "visibility_off"
size: 18
color: allModeToggle.isAllowed ? Theme.primary : Theme.surfaceVariantText
}
}
Row {
id: rightContent
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Rectangle {
id: allModeToggle
visible: root.item?.type === "plugin_browse"
width: 28
height: 28
radius: 14
anchors.verticalCenter: parent.verticalCenter
color: allModeToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
property bool isAllowed: {
if (root.item?.type !== "plugin_browse")
return false;
MouseArea {
id: allModeToggleArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
var pluginId = root.item?.data?.pluginId;
if (!pluginId)
return false;
SettingsData.launcherPluginVisibility;
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
return;
SettingsData.setPluginAllowWithoutTrigger(pluginId, !allModeToggle.isAllowed);
}
}
}
DankIcon {
anchors.centerIn: parent
name: allModeToggle.isAllowed ? "visibility" : "visibility_off"
size: 18
color: allModeToggle.isAllowed ? Theme.primary : Theme.surfaceVariantText
}
Rectangle {
visible: !!root.item?.type && root.item.type !== "app" && root.item.type !== "plugin_browse"
width: typeBadge.implicitWidth + Theme.spacingS * 2
height: 20
radius: 10
color: Theme.surfaceVariantAlpha
anchors.verticalCenter: parent.verticalCenter
MouseArea {
id: allModeToggleArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
var pluginId = root.item?.data?.pluginId;
if (!pluginId)
return;
SettingsData.setPluginAllowWithoutTrigger(pluginId, !allModeToggle.isAllowed);
StyledText {
id: typeBadge
anchors.centerIn: parent
text: {
if (!root.item)
return "";
switch (root.item.type) {
case "plugin":
return I18n.tr("Plugin");
case "file":
return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default:
return "";
}
}
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.surfaceVariantText
}
}
Rectangle {
visible: !!root.item?.type && root.item.type !== "app" && root.item.type !== "plugin_browse"
width: typeBadge.implicitWidth + Theme.spacingS * 2
height: 20
radius: 10
color: Theme.surfaceVariantAlpha
anchors.verticalCenter: parent.verticalCenter
StyledText {
id: typeBadge
anchors.centerIn: parent
text: {
if (!root.item)
return "";
switch (root.item.type) {
case "plugin":
return I18n.tr("Plugin");
case "file":
return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default:
return "";
}
}
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.surfaceVariantText
}
}
SourceBadge {
anchors.verticalCenter: parent.verticalCenter
source: root.item?.type === "app" ? (root.item.source || "") : ""
glyphSize: 14
}
SourceBadge {
anchors.verticalCenter: parent.verticalCenter
source: root.item?.type === "app" ? (root.item.source || "") : ""
glyphSize: 14
}
}
}