[plasma/plasma-desktop] emojier: emojier: allow zooming the emoji grid
Nate Graham <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 5d6be2cec17cefb3fbedb591fd7b7c543c721651 by Nate Graham, on behalf of Jason Uithol.
Committed on 27/07/2026 at 19:45.
Pushed by ngraham into branch 'master'.
emojier: allow zooming the emoji grid
You can now zoom with Ctrl+scroll, keyboard shortcuts, and toolbar buttons.
This enlarges or shrinks the cells of the emoji grid, clamped between 1.5 and
8 grid units. Plain scrolling is unaffected and still scrolls the list.
The glyph font size is derived from the cell height instead of a fixed point
size, so it scales with the zoom (this also lets the inert minimumPointSize be
dropped). Size changes are coalesced on a short throttle so a fast wheel spin
triggers at most one relayout per frame instead of one per event. The skin-tone
variant popup tracks the same size.
M +105 -4 emojier/app/ui/CategoryPage.qml
M +1 -1 emojier/app/ui/Emojier.qml
M +17 -0 emojier/emojierplugin.cpp
M +7 -0 emojier/emojierplugin.h
M +3 -0 emojier/emojiersettings.kcfg
https://invent.kde.org/plasma/plasma-desktop/-/commit/5d6be2cec17cefb3fbedb591fd7b7c543c721651
diff --git a/emojier/app/ui/CategoryPage.qml b/emojier/app/ui/CategoryPage.qml
index cb5a705dcc..f66eaa0119 100644
--- a/emojier/app/ui/CategoryPage.qml
+++ b/emojier/app/ui/CategoryPage.qml
@@ -187,6 +187,39 @@ Kirigami.ScrollablePage {
checked: view.model.skinTone == SkinTone.Dark
onTriggered: view.model.skinTone = SkinTone.Dark
}
+ },
+ Kirigami.Action {
+ icon.name: "zoom-out"
+ displayHint: Kirigami.DisplayHint.IconOnly
+ enabled: emojiView.targetSize > emojiView.minSize + 0.5
+ text: i18nc("@action:button zoom the emoji grid out", "Zoom Out")
+ onTriggered: emojiView.applyZoom(-1)
+ },
+ Kirigami.Action {
+ text: i18nc("@info:status label for the current zoom-level indicator", "Zoom level")
+ displayComponent: QQC2.Label {
+ text: i18nc("@info:status current emoji zoom level as a percentage, %1 is a number", "%1%",
+ Math.round(emojiView.targetSize / emojiView.defaultSize * 100))
+ textFormat: Text.PlainText
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: Kirigami.Units.smallSpacing
+ rightPadding: Kirigami.Units.smallSpacing
+ }
+ },
+ Kirigami.Action {
+ icon.name: "zoom-in"
+ displayHint: Kirigami.DisplayHint.IconOnly
+ enabled: emojiView.targetSize < emojiView.maxSize - 0.5
+ text: i18nc("@action:button zoom the emoji grid in", "Zoom In")
+ onTriggered: emojiView.applyZoom(1)
+ },
+ Kirigami.Action {
+ icon.name: "zoom-original"
+ displayHint: Kirigami.DisplayHint.IconOnly
+ enabled: Math.abs(emojiView.targetSize - emojiView.defaultSize) > 0.5
+ text: i18nc("@action:button reset the emoji grid zoom to 100%", "Reset Zoom")
+ onTriggered: emojiView.resetZoom()
}
]
@@ -200,7 +233,8 @@ Kirigami.ScrollablePage {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: contentHeight
- readonly property real desiredSize: Kirigami.Units.gridUnit * 3
+ // Track the main grid's zoom so variants match the chosen size.
+ readonly property real desiredSize: emojiView.desiredSize
cellWidth: desiredSize
cellHeight: desiredSize
@@ -229,6 +263,22 @@ Kirigami.ScrollablePage {
}
}
+ Shortcut {
+ sequences: [StandardKey.ZoomIn]
+ enabled: emojiView.targetSize < emojiView.maxSize - 0.5
+ onActivated: emojiView.applyZoom(1)
+ }
+ Shortcut {
+ sequences: [StandardKey.ZoomOut]
+ enabled: emojiView.targetSize > emojiView.minSize + 0.5
+ onActivated: emojiView.applyZoom(-1)
+ }
+ Shortcut {
+ sequence: "Ctrl+0"
+ enabled: Math.abs(emojiView.targetSize - emojiView.defaultSize) > 0.5
+ onActivated: emojiView.resetZoom()
+ }
+
Component {
id: menuComponent
@@ -260,14 +310,66 @@ Kirigami.ScrollablePage {
GridView {
id: emojiView
- readonly property real desiredSize: Kirigami.Units.gridUnit * 3
+ readonly property real defaultSize: Kirigami.Units.gridUnit * 3
+ readonly property real minSize: Kirigami.Units.gridUnit * 1.5
+ readonly property real maxSize: defaultSize * 2.75
+ readonly property real zoomStep: 0.25
+
+ property real desiredSize: defaultSize
+ // Ctrl+wheel zoom accumulates into targetSize; desiredSize (which drives
+ // the relayout) is updated on a throttle so a fast wheel spin coalesces
+ // into at most one relayout per frame instead of one per event.
+ property real targetSize: defaultSize
readonly property int columnsToHave: Math.ceil(width / desiredSize)
readonly property int delayInterval: Math.min(300, columnsToHave * 10)
property int hoveredIndex: -1
+ function applyZoom(steps: int): void {
+ const stepPx = defaultSize * zoomStep
+ const index = Math.round((targetSize - defaultSize) / stepPx) + steps
+ targetSize = Math.max(minSize, Math.min(maxSize, defaultSize + index * stepPx))
+ emoji.emojiZoom = targetSize / defaultSize
+ if (!zoomThrottle.running) {
+ desiredSize = targetSize
+ zoomThrottle.start()
+ }
+ }
+
+ function resetZoom(): void {
+ targetSize = defaultSize
+ desiredSize = defaultSize
+ emoji.emojiZoom = 1
+ }
+
+ // Clamp in case the stored zoom or the size bounds changed since it was saved.
+ Component.onCompleted: {
+ const saved = Math.max(minSize, Math.min(maxSize, defaultSize * emoji.emojiZoom))
+ targetSize = saved
+ desiredSize = saved
+ }
+
cellWidth: width / columnsToHave
cellHeight: desiredSize
+ Timer {
+ id: zoomThrottle
+ interval: 33 // ms; caps relayouts at ~30 Hz so a fast spin coalesces
+ onTriggered: emojiView.desiredSize = emojiView.targetSize
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.NoButton
+ onWheel: wheel => {
+ if (wheel.modifiers & Qt.ControlModifier) {
+ emojiView.applyZoom(wheel.angleDelta.y > 0 ? 1 : -1)
+ wheel.accepted = true
+ } else {
+ wheel.accepted = false
+ }
+ }
+ }
+
model: CategoryModelFilter {
id: filter
sourceModel: SearchModelFilter {
@@ -322,9 +424,8 @@ Kirigami.ScrollablePage {
text: model.display
contentItem: QQC2.Label {
- font.pointSize: 25
+ font.pixelSize: Math.round(emojiLabel.height * 0.6)
font.family: 'emoji' // Avoid monochrome fonts like DejaVu Sans
- minimumPointSize: 10
text: emojiLabel.text
textFormat: Text.PlainText
horizontalAlignment: Text.AlignHCenter
diff --git a/emojier/app/ui/Emojier.qml b/emojier/app/ui/Emojier.qml
index 4d6e647c60..ac2aaea021 100644
--- a/emojier/app/ui/Emojier.qml
+++ b/emojier/app/ui/Emojier.qml
@@ -22,7 +22,7 @@ Kirigami.ApplicationWindow {
minimumWidth: Math.round(minimumHeight * 1.25)
// minimumHeight is set in Component.onCompleted to avoid a binding loop
- width: Kirigami.Units.gridUnit * 25
+ width: Kirigami.Units.gridUnit * 27
height: Kirigami.Units.gridUnit * 25
pageStack.initialPage: Qt.resolvedUrl("CategoryPage.qml")// { }
diff --git a/emojier/emojierplugin.cpp b/emojier/emojierplugin.cpp
index 797842d8e3..9c54fc1257 100644
--- a/emojier/emojierplugin.cpp
+++ b/emojier/emojierplugin.cpp
@@ -205,6 +205,23 @@ int EmojiModel::skinTone() const
return m_settings.skinTone();
}
+void EmojiModel::setEmojiZoom(qreal emojiZoom)
+{
+ if (m_settings.emojiZoom() == emojiZoom) {
+ return;
+ }
+ m_settings.setEmojiZoom(emojiZoom);
+ // Persist immediately rather than at destruction: the emojier is a
+ // launch-pick-close popup, and its recent list is saved the same way.
+ m_settings.save();
+ Q_EMIT emojiZoomChanged();
+}
+
+qreal EmojiModel::emojiZoom() const
+{
+ return m_settings.emojiZoom();
+}
+
RecentEmojiModel::RecentEmojiModel()
{
refresh();
diff --git a/emojier/emojierplugin.h b/emojier/emojierplugin.h
index 9b14abebfa..d668bc9efb 100644
--- a/emojier/emojierplugin.h
+++ b/emojier/emojierplugin.h
@@ -100,6 +100,7 @@ class EmojiModel : public AbstractEmojiModel
QML_ELEMENT
Q_PROPERTY(QStringList categories MEMBER m_categories CONSTANT)
Q_PROPERTY(int skinTone READ skinTone WRITE setSkinTone NOTIFY skinToneChanged)
+ Q_PROPERTY(qreal emojiZoom READ emojiZoom WRITE setEmojiZoom NOTIFY emojiZoomChanged)
Q_PROPERTY(QAbstractItemModel *twoToneEmojiModel MEMBER m_TwoToneEmojiModel CONSTANT)
public:
enum EmojiRole {
@@ -117,6 +118,12 @@ public:
Q_SIGNAL void skinToneChanged();
+ qreal emojiZoom() const;
+
+ void setEmojiZoom(qreal emojiZoom);
+
+ Q_SIGNAL void emojiZoomChanged();
+
private:
QStringList m_categories;
TwoToneEmojiModel *m_TwoToneEmojiModel;
diff --git a/emojier/emojiersettings.kcfg b/emojier/emojiersettings.kcfg
index 9b22780a59..848f42db30 100644
--- a/emojier/emojiersettings.kcfg
+++ b/emojier/emojiersettings.kcfg
@@ -17,5 +17,8 @@
</choices>
<default>Neutral</default>
</entry>
+ <entry name="emojiZoom" type="Double">
+ <default>1.0</default>
+ </entry>
</group>
</kcfg>