[gnome-shell/gbsneto/icon-grid-part4: 1/2] Introduce EditableLabel
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/gbsneto/icon-grid-part4: 1/2] Introduce EditableLabel
- Date: Thu, 8 Aug 2019 19:55:17 +0000 (UTC)
commit 59237f150e4bc163e485e05200b078983b55f054
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date: Thu Aug 8 16:51:02 2019 -0300
Introduce EditableLabel
EditableLabel is, as stated by its name, a label
that is editable. It will be used by folder icons
to allow editing their names.
WIP: theme
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/675
data/theme/gnome-shell-sass/_common.scss | 21 +++
js/js-resources.gresource.xml | 1 +
js/ui/editableLabel.js | 242 +++++++++++++++++++++++++++++++
3 files changed, 264 insertions(+)
---
diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss
index 8731ba48d9..6536f64c62 100644
--- a/data/theme/gnome-shell-sass/_common.scss
+++ b/data/theme/gnome-shell-sass/_common.scss
@@ -101,6 +101,27 @@ StEntry {
}
}
+/* Editable Label */
+.overview-icon-label {
+ padding: 0;
+
+ border: transparent;
+ border-width: 1px;
+ background-color: transparent;
+
+ &:focus {
+ background-color: $base_color;
+ border-width: 1px;
+ border-radius: $button_radius;
+ color: $fg_color;
+ }
+
+ &:highlighted,
+ &:focus:highlighted {
+ border-radius: $button_radius;
+ }
+}
+
/* Scrollbars */
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index b5348ddcb5..b7b61674d9 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -51,6 +51,7 @@
<file>ui/dialog.js</file>
<file>ui/dnd.js</file>
<file>ui/edgeDragAction.js</file>
+ <file>ui/editableLabel.js</file>
<file>ui/endSessionDialog.js</file>
<file>ui/environment.js</file>
<file>ui/extensionDownloader.js</file>
diff --git a/js/ui/editableLabel.js b/js/ui/editableLabel.js
new file mode 100644
index 0000000000..7807713bbe
--- /dev/null
+++ b/js/ui/editableLabel.js
@@ -0,0 +1,242 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported EditableLabel */
+
+const { Clutter, GObject, Pango, St } = imports.gi;
+
+const GrabHelper = imports.ui.grabHelper;
+const Lang = imports.lang;
+
+var EditableLabelMode = {
+ DISPLAY: 0,
+ HIGHLIGHT: 1,
+ EDIT: 2
+};
+
+var EditableLabel = GObject.registerClass({
+ Signals: {
+ 'label-edit-update': { param_types: [ GObject.TYPE_STRING ] },
+ 'label-edit-cancel': {}
+ }
+}, class EditableLabel extends St.Entry {
+ _init(params) {
+ super._init(params);
+
+ this.clutter_text.set({
+ editable: false,
+ ellipsize: Pango.EllipsizeMode.END,
+ selectable: false,
+ x_align: Clutter.ActorAlign.CENTER,
+ y_align: Clutter.ActorAlign.CENTER
+ });
+
+ this.clutter_text.bind_property('editable',
+ this.clutter_text, 'selectable',
+ GObject.BindingFlags.BIDIRECTIONAL |
+ GObject.BindingFlags.SYNC_CREATE);
+
+ this._activateId = 0;
+ this._keyFocusId = 0;
+ this._labelMode = EditableLabelMode.DISPLAY;
+ this._oldLabelText = null;
+
+ this.connect('button-press-event', this._onButtonPressEvent.bind(this));
+ this.connect('destroy', this._onDestroy.bind(this));
+
+ this._grabHelper = new GrabHelper.GrabHelper(this);
+ this._grabHelper.addActor(this);
+ }
+
+ _onDestroy() {
+ this._cancelEditing();
+ }
+
+ _onButtonPressEvent(label, event) {
+ if (event.get_button() != Clutter.BUTTON_PRIMARY)
+ return false;
+
+ if (event.get_click_count() > 1 &&
+ this._labelMode != EditableLabelMode.HIGHLIGHT)
+ return false;
+
+ // enter highlight mode if this is the first click
+ if (this._labelMode == EditableLabelMode.DISPLAY) {
+ this.setMode(EditableLabelMode.HIGHLIGHT);
+ return true;
+ }
+
+ if (this._labelMode == EditableLabelMode.HIGHLIGHT) {
+ // while in highlight mode, another extra click enters the
+ // actual edit mode
+ this.setMode(EditableLabelMode.EDIT);
+ return true;
+ }
+
+ // ensure focus stays in the text field when clicking
+ // on the entry empty space
+ this.grab_key_focus();
+
+ let [stageX, stageY] = event.get_coords();
+ let [textX, textY] = this.clutter_text.get_transformed_position();
+
+ if (stageX < textX) {
+ this.clutter_text.cursor_position = 0;
+ this.clutter_text.set_selection(0, 0);
+ } else {
+ this.clutter_text.cursor_position = -1;
+ this.clutter_text.selection_bound = -1;
+ }
+
+ // eat button press events on the entry empty space in this mode
+ return true;
+ }
+
+ _onHighlightUngrab(isUser) {
+ // exit highlight mode
+ this.remove_style_pseudo_class('highlighted');
+
+ // clicked outside the label - cancel the edit
+ if (isUser) {
+ this.setMode(EditableLabelMode.DISPLAY);
+ this.emit('label-edit-cancel');
+ return;
+ }
+
+ // now prepare for editing...
+ this._keyFocusId = this.connect('key-focus-in', this._startEditing.bind(this));
+ this._grabHelper.grab({ actor: this,
+ focus: this,
+ onUngrab: this._onEditUngrab.bind(this) });
+ }
+
+ _onEditUngrab(isUser) {
+ // edit has already been completed and this is an explicit
+ // ungrab from endEditing()
+ if (!isUser)
+ return;
+
+ let event = Clutter.get_current_event();
+ let eventType;
+
+ if (event)
+ eventType = event.type();
+
+ if (eventType == Clutter.EventType.KEY_PRESS) {
+ let symbol = event.get_key_symbol();
+
+ // abort editing
+ if (symbol == Clutter.KEY_Escape)
+ this._cancelEditing();
+
+ return;
+ }
+
+ // confirm editing when clicked outside the label
+ if (eventType == Clutter.EventType.BUTTON_PRESS) {
+ this._confirmEditing();
+ return;
+ }
+
+ // abort editing for other grab-breaking events
+ this._cancelEditing();
+ }
+
+ _startEditing() {
+ let text = this.get_text();
+
+ // Select the current text when editing starts
+ this.clutter_text.set({
+ cursor_position: 0,
+ editable: true,
+ selection_bound: text.length
+ });
+
+ // Save the current contents of the label, in case we
+ // need to roll back
+ this._oldLabelText = text;
+
+ this._activateId = this.clutter_text.connect('activate', this._confirmEditing.bind(this));
+ }
+
+ _endEditing() {
+ this.clutter_text.editable = false;
+
+ this._oldLabelText = null;
+
+ if (this._activateId) {
+ this.clutter_text.disconnect(this._activateId);
+ this._activateId = 0;
+ }
+
+ if (this._keyFocusId) {
+ this.disconnect(this._keyFocusId);
+ this._keyFocusId = 0;
+ }
+
+ if (this._grabHelper.isActorGrabbed(this))
+ this._grabHelper.ungrab({ actor: this });
+
+ // Ensure the focus style is removed, because moving the focus
+ // programmatically (without a click in another actor) doesn't
+ // apparently remove it
+ this.remove_style_pseudo_class('focus');
+ }
+
+ _cancelEditing() {
+ // setting the mode to DISPLAY below will unset oldLabelText
+ let oldText = this._oldLabelText;
+
+ this.setMode(EditableLabelMode.DISPLAY);
+
+ this.set_text(oldText);
+ this.emit('label-edit-cancel');
+ }
+
+ _confirmEditing() {
+ // setting the mode to DISPLAY below will unset oldLabelText
+ let oldText = this._oldLabelText;
+ let text = this.get_text();
+
+ if (!text || text == oldText) {
+ this._cancelEditing();
+ return;
+ }
+
+ this.setMode(EditableLabelMode.DISPLAY);
+ this.emit('label-edit-update', text);
+ }
+
+ setMode(mode) {
+ if (this._labelMode == mode)
+ return;
+
+ switch (mode) {
+ case EditableLabelMode.DISPLAY:
+ this._endEditing();
+ break;
+
+ case EditableLabelMode.HIGHLIGHT:
+ this.add_style_pseudo_class('highlighted');
+ this._grabHelper.grab({
+ actor: this,
+ onUngrab: this._onHighlightUngrab.bind(this)
+ });
+ break;
+
+ case EditableLabelMode.EDIT:
+ // we need to be in highlight mode before reaching the edit mode
+ if (this._labelMode != EditableLabelMode.HIGHLIGHT)
+ this.setMode(EditableLabelMode.HIGHLIGHT);
+
+ // enter the edit mode on highlight ungrab
+ if (this._grabHelper.grabbed)
+ this._grabHelper.ungrab({ actor: this });
+ this.grab_key_focus();
+ break;
+
+ default:
+ throw new Error(`Invalid mode for EditableLabel: ${mode}`);
+ }
+
+ this._labelMode = mode;
+ }
+});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]