132 lines
5.2 KiB
JavaScript
132 lines
5.2 KiB
JavaScript
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
import St from 'gi://St';
|
|
import {Button as PanelButton} from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
|
import Clutter from 'gi://Clutter';
|
|
import {gnomeExecutables, autocomplete, clear} from './autocomplete.js';
|
|
import {launchApp} from './utils.js';
|
|
export class TopBarSearchEntry {
|
|
_searchButton;
|
|
_searchContainer;
|
|
_searchEntry;
|
|
_searchSuggestion;
|
|
_alive;
|
|
constructor(settings) {
|
|
this._alive = true;
|
|
this._searchButton = new PanelButton(0.0, 'searchEntry', false);
|
|
this._searchContainer = new St.Bin({
|
|
x_expand: true,
|
|
});
|
|
this._searchEntry = new St.Entry({
|
|
style_class: 'custom-search-entry',
|
|
can_focus: true,
|
|
hint_text: 'Type to search…',
|
|
track_hover: true,
|
|
x_expand: true,
|
|
});
|
|
this._searchSuggestion = new St.Label({
|
|
style_class: 'suggestion',
|
|
text: '',
|
|
x_expand: true,
|
|
});
|
|
this._searchEntry.clutter_text.connect('notify::mapped', actor => {
|
|
if (actor.mapped)
|
|
actor.grab_key_focus();
|
|
});
|
|
this._searchContainer.add_child(this._searchSuggestion);
|
|
this._searchContainer.add_child(this._searchEntry);
|
|
this._searchEntry.clutter_text.connect('text-changed', actor => {
|
|
let current = actor.get_text();
|
|
if (current.length > 1 && this._searchSuggestion) {
|
|
let matches = autocomplete(current);
|
|
if (matches.length > 0 && this._searchEntry) {
|
|
let match = matches[0];
|
|
const ct = this._searchEntry.get_clutter_text();
|
|
const layout = ct.get_layout();
|
|
const [textW] = layout.get_pixel_size();
|
|
const themeNode = this._searchEntry.get_theme_node();
|
|
const leftPad = themeNode.get_padding(St.Side.LEFT);
|
|
const x = leftPad + textW;
|
|
this._searchSuggestion.set_style(`color: rgba(255,255,255,0.35); margin-left: ${x + 4}px;`);
|
|
this._searchSuggestion.set_text(match.slice(current.length));
|
|
} else {
|
|
this._searchSuggestion.set_text('');
|
|
}
|
|
} else if (this._searchSuggestion) {
|
|
this._searchSuggestion.set_text('');
|
|
}
|
|
});
|
|
let completeText = () => {
|
|
const typed = this._searchEntry?.get_text();
|
|
const ct = this._searchEntry?.get_clutter_text();
|
|
if (!typed || !ct)
|
|
return;
|
|
let full = typed + this._searchSuggestion?.get_text();
|
|
this._searchEntry?.set_text(full);
|
|
ct.set_cursor_position(full.length);
|
|
this._searchSuggestion?.set_text('');
|
|
};
|
|
this._searchEntry.clutter_text.connect('key-press-event', (ct, event) => {
|
|
const key = event.get_key_symbol();
|
|
if (key === Clutter.KEY_KP_Right || key === Clutter.KEY_Right) {
|
|
// Only accept if cursor is at end and a suggestion exists
|
|
const typed = this._searchEntry?.get_text();
|
|
if (typed) {
|
|
completeText();
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
});
|
|
this._searchEntry.connect('captured-event', (actor, event) => {
|
|
if (event.type() !== Clutter.EventType.KEY_PRESS)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
const sym = event.get_key_symbol();
|
|
if (sym === Clutter.KEY_Tab || sym === Clutter.KEY_ISO_Left_Tab) {
|
|
completeText();
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
});
|
|
this._searchEntry.clutter_text.connect('activate', actor => {
|
|
let query = actor.get_text().trim().toLowerCase();
|
|
if (query === '') {
|
|
this.destroy();
|
|
} else if (gnomeExecutables?.get(query)) {
|
|
gnomeExecutables.get(query)?.launch([], null);
|
|
this.destroy();
|
|
} else if (launchApp([query])) {
|
|
this.destroy();
|
|
} else {
|
|
actor.set_text('');
|
|
this._searchEntry?.set_style('border: 2px solid red;');
|
|
}
|
|
});
|
|
this._searchButton.add_child(this._searchContainer);
|
|
let positionInt = settings.get_int('search-entry-position');
|
|
let position;
|
|
if (positionInt === 0)
|
|
position = 'left';
|
|
else if (positionInt === 1)
|
|
position = 'center';
|
|
else
|
|
position = 'right';
|
|
Main.panel.addToStatusArea('SearchEntry', this._searchButton, 0, position);
|
|
}
|
|
|
|
isAlive() {
|
|
return this._alive;
|
|
}
|
|
|
|
destroy() {
|
|
this._alive = false;
|
|
if (this._searchButton) {
|
|
this._searchButton?.destroy();
|
|
this._searchContainer = undefined;
|
|
this._searchEntry = undefined;
|
|
this._searchButton = undefined;
|
|
this._searchSuggestion = undefined;
|
|
}
|
|
clear();
|
|
}
|
|
}
|