Merge pull request #896 from eric-handley/feat/improve-emoji-selector

Improve >emoji selector with category drawers
This commit is contained in:
Lysec
2025-11-29 08:06:25 +01:00
committed by GitHub
5 changed files with 12680 additions and 119 deletions
+130 -22
View File
@@ -61,6 +61,9 @@ SmartPanel {
if (searchText.startsWith(">clip") || searchText.startsWith(">calc")) {
return false;
}
if (activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
return true;
}
return Settings.data.appLauncher.viewMode === "grid";
}
@@ -77,7 +80,14 @@ SmartPanel {
// They are not coming from SmartPanelWindow as they are consumed by the search field before reaching the panel.
// They are instead being forwared from the search field NTextInput below.
function onTabPressed() {
selectNextWrapped();
// In emoji browsing mode, Tab navigates between categories
if (activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
var currentIndex = emojiPlugin.categories.indexOf(emojiPlugin.selectedCategory);
var nextIndex = (currentIndex + 1) % emojiPlugin.categories.length;
emojiPlugin.selectCategory(emojiPlugin.categories[nextIndex]);
} else {
selectNextWrapped();
}
}
function onBackTabPressed() {
@@ -616,6 +626,33 @@ SmartPanel {
}
}
// Emoji category tabs (shown when in browsing mode)
NTabBar {
id: emojiCategoryTabs
visible: root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode
Layout.fillWidth: true
currentIndex: {
if (visible && emojiPlugin.categories) {
return emojiPlugin.categories.indexOf(emojiPlugin.selectedCategory);
}
return 0;
}
Repeater {
model: emojiPlugin.categories
NIconTabButton {
required property string modelData
required property int index
icon: emojiPlugin.categoryIcons[modelData] || "star"
tabIndex: index
checked: emojiCategoryTabs.currentIndex === index
onClicked: {
emojiPlugin.selectCategory(modelData);
}
}
}
}
Loader {
id: resultsViewLoader
Layout.fillWidth: true
@@ -774,8 +811,8 @@ SmartPanel {
NText {
id: emojiDisplay
anchors.centerIn: parent
visible: modelData.emojiChar ? true : (!imagePreview.visible && !iconLoader.visible)
text: modelData.emojiChar ? modelData.emojiChar : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?")
visible: modelData.emojiChar || (!imagePreview.visible && !iconLoader.visible)
text: modelData.emojiChar ? modelData.emojiChar : modelData.name.charAt(0).toUpperCase()
pointSize: modelData.emojiChar ? Style.fontSizeXXXL : Style.fontSizeXXL // Larger font for emojis
font.weight: Style.fontWeightBold
color: modelData.emojiChar ? Color.mOnSurface : Color.mOnPrimary // Different color for emojis
@@ -877,8 +914,18 @@ SmartPanel {
width: parent.width
height: parent.height
cellWidth: gridCellSize + Style.marginXXS
cellHeight: gridCellSize + Style.marginXXS
cellWidth: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
return parent.width / 5;
}
return gridCellSize + Style.marginXXS;
}
cellHeight: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
return (parent.width / 5) * 1.2;
}
return gridCellSize + Style.marginXXS;
}
model: results
cacheBuffer: resultsGrid.height * 2
keyNavigationEnabled: false
@@ -888,9 +935,14 @@ SmartPanel {
onWidthChanged: {
// Update gridColumns based on actual GridView width
// This ensures navigation works correctly regardless of panel size
const actualCols = Math.floor(width / cellWidth);
if (actualCols > 0 && actualCols !== root.gridColumns) {
root.gridColumns = actualCols;
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
// Always 5 columns for emoji browsing mode
root.gridColumns = 5;
} else {
const actualCols = Math.floor(width / cellWidth);
if (actualCols > 0 && actualCols !== root.gridColumns) {
root.gridColumns = actualCols;
}
}
}
@@ -903,6 +955,16 @@ SmartPanel {
onModelChanged: {}
// Update gridColumns when entering/exiting emoji browsing mode
Connections {
target: emojiPlugin
function onIsBrowsingModeChanged() {
if (emojiPlugin.isBrowsingMode) {
root.gridColumns = 5;
}
}
}
// Handle scrolling to show selected item when it changes
Connections {
target: root
@@ -934,8 +996,18 @@ SmartPanel {
property bool isSelected: (!root.ignoreMouseHover && mouseArea.containsMouse) || (index === selectedIndex)
property string appId: (modelData && modelData.appId) ? String(modelData.appId) : ""
width: gridCellSize
height: gridCellSize
width: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
return resultsGrid.width / 5;
}
return gridCellSize;
}
height: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
return (resultsGrid.width / 5) * 1.2;
}
return gridCellSize;
}
radius: Style.radiusM
color: gridEntry.isSelected ? Color.mHover : Color.mSurface
@@ -948,13 +1020,28 @@ SmartPanel {
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginM
anchors.margins: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
return 4;
}
return Style.marginM;
}
spacing: Style.marginS
// Icon badge or Image preview or Emoji
Rectangle {
Layout.preferredWidth: badgeSize * 1.5
Layout.preferredHeight: badgeSize * 1.5
Layout.preferredWidth: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode && modelData.emojiChar) {
return gridEntry.width - 8;
}
return badgeSize * 1.5;
}
Layout.preferredHeight: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode && modelData.emojiChar) {
return gridEntry.width - 8;
}
return badgeSize * 1.5;
}
Layout.alignment: Qt.AlignHCenter
radius: Style.radiusM
color: Color.mSurfaceVariant
@@ -1016,9 +1103,17 @@ SmartPanel {
NText {
id: gridEmojiDisplay
anchors.centerIn: parent
visible: modelData.emojiChar ? true : (!gridImagePreview.visible && !gridIconLoader.visible)
text: modelData.emojiChar ? modelData.emojiChar : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?")
pointSize: modelData.emojiChar ? Style.fontSizeXXL : Style.fontSizeXL
visible: modelData.emojiChar || (!gridImagePreview.visible && !gridIconLoader.visible)
text: modelData.emojiChar ? modelData.emojiChar : modelData.name.charAt(0).toUpperCase()
pointSize: {
if (modelData.emojiChar) {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode) {
return Math.max(Style.fontSizeL, gridEntry.width * 0.4);
}
return Style.fontSizeXXL * 2;
}
return Style.fontSizeXL;
}
font.weight: Style.fontWeightBold
color: modelData.emojiChar ? Color.mOnSurface : Color.mOnPrimary
}
@@ -1027,13 +1122,20 @@ SmartPanel {
// Text content
NText {
text: modelData.name || "Unknown"
pointSize: Style.fontSizeS
pointSize: {
if (root.activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode && modelData.emojiChar) {
return Style.fontSizeS;
}
return Style.fontSizeS;
}
font.weight: Style.fontWeightSemiBold
color: gridEntry.isSelected ? Color.mOnHover : Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
Layout.maximumWidth: gridCellSize - Style.marginM * 2
Layout.maximumWidth: gridEntry.width - 8
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.NoWrap
maximumLineCount: 1
}
}
@@ -1068,10 +1170,16 @@ SmartPanel {
NText {
Layout.fillWidth: true
text: {
if (results.length === 0)
return searchText ? "No results" : "";
const prefix = activePlugin?.name ? `${activePlugin.name}: ` : "";
return prefix + `${results.length} result${results.length !== 1 ? 's' : ''}`;
if (results.length === 0) {
if (searchText) {
return "No results";
} else if (activePlugin === emojiPlugin && emojiPlugin.isBrowsingMode && emojiPlugin.selectedCategory === "recent") {
return "No recently used emoji";
}
return "";
}
var prefix = activePlugin && activePlugin.name ? activePlugin.name + ": " : "";
return prefix + results.length + " result" + (results.length !== 1 ? 's' : '');
}
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
@@ -11,13 +11,30 @@ Item {
property var launcher: null
property bool handleSearch: false
property string selectedCategory: "recent"
property bool isBrowsingMode: false
property var categoryIcons: ({
"recent": "clock",
"people": "user",
"animals": "paw",
"nature": "leaf",
"food": "apple",
"activity": "run",
"travel": "plane",
"objects": "home",
"symbols": "star",
"flags": "flag"
})
property var categories: ["recent", "people", "animals", "nature", "food", "activity", "travel", "objects", "symbols", "flags"]
// Force update results when emoji service loads
Connections {
target: EmojiService
function onLoadedChanged() {
if (EmojiService.loaded && root.launcher) {
// Update launcher results to refresh the UI
root.launcher?.updateResults();
root.launcher.updateResults();
}
}
}
@@ -27,6 +44,18 @@ Item {
Logger.i("EmojiPlugin", "Initialized");
}
function selectCategory(category) {
selectedCategory = category;
if (launcher) {
launcher.updateResults();
}
}
function onOpened() {
// Always reset to "recent" category when opening
selectedCategory = "recent";
}
// Check if this plugin handles the command
function handleCommand(searchText) {
return searchText.startsWith(">emoji");
@@ -65,15 +94,23 @@ Item {
];
}
const query = searchText.slice(6).trim();
const emojis = EmojiService.search(query);
return emojis.map(formatEmojiEntry);
var query = searchText.slice(6).trim();
if (query === "") {
isBrowsingMode = true;
var emojis = EmojiService.getEmojisByCategory(selectedCategory);
return emojis.map(formatEmojiEntry);
} else {
isBrowsingMode = false;
var emojis = EmojiService.search(query);
return emojis.map(formatEmojiEntry);
}
}
// Format an emoji entry for the results list
function formatEmojiEntry(emoji) {
let title = emoji.name;
let description = (emoji.keywords || []).join(", ");
let description = emoji.keywords.join(", ");
if (emoji.category) {
description += " • Category: " + emoji.category;