132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
import Clutter from 'gi://Clutter';
|
|
import Pango from 'gi://Pango';
|
|
import St from 'gi://St';
|
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
import {Logger} from "../utils/logger.js";
|
|
import {WindowWrapper} from "./window.js";
|
|
import {Rect} from "../utils/rect.js";
|
|
|
|
export const TAB_BAR_HEIGHT = 24;
|
|
|
|
type TabClickedCallback = (index: number) => void;
|
|
|
|
export class TabBar {
|
|
private _bar: St.BoxLayout;
|
|
private _buttons: St.Button[] = [];
|
|
private _activeIndex: number = 0;
|
|
private _onTabClicked: TabClickedCallback;
|
|
private _visible: boolean = false;
|
|
|
|
constructor(onTabClicked: TabClickedCallback) {
|
|
this._onTabClicked = onTabClicked;
|
|
this._bar = new St.BoxLayout({
|
|
style_class: 'aerospike-tab-bar',
|
|
vertical: false,
|
|
reactive: true,
|
|
can_focus: false,
|
|
track_hover: false,
|
|
});
|
|
// Force all tabs to equal width regardless of text length
|
|
(this._bar.layout_manager as Clutter.BoxLayout).homogeneous = true;
|
|
}
|
|
|
|
/**
|
|
* Rebuild all tab buttons from the current list of window items.
|
|
*/
|
|
rebuild(items: WindowWrapper[], activeIndex: number): void {
|
|
// Remove old buttons
|
|
this._bar.destroy_all_children();
|
|
this._buttons = [];
|
|
|
|
items.forEach((item, index) => {
|
|
const label = new St.Label({
|
|
text: item.getTabLabel(),
|
|
style_class: 'aerospike-tab-label',
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
x_expand: true,
|
|
});
|
|
label.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
|
|
|
const button = new St.Button({
|
|
style_class: 'aerospike-tab',
|
|
reactive: true,
|
|
can_focus: false,
|
|
track_hover: true,
|
|
x_expand: true,
|
|
child: label,
|
|
});
|
|
|
|
button.connect('clicked', () => {
|
|
this._onTabClicked(index);
|
|
});
|
|
|
|
this._bar.add_child(button);
|
|
this._buttons.push(button);
|
|
});
|
|
|
|
this.setActive(activeIndex);
|
|
}
|
|
|
|
/**
|
|
* Update just the title text of a single tab (e.g. when a window title changes).
|
|
*/
|
|
updateTabTitle(index: number, title: string): void {
|
|
if (index < 0 || index >= this._buttons.length) return;
|
|
const label = this._buttons[index].get_child() as St.Label;
|
|
if (label) label.set_text(title);
|
|
}
|
|
|
|
/**
|
|
* Highlight the active tab and dim the rest.
|
|
*/
|
|
setActive(index: number): void {
|
|
this._activeIndex = index;
|
|
this._buttons.forEach((btn, i) => {
|
|
if (i === index) {
|
|
btn.add_style_class_name('aerospike-tab-active');
|
|
} else {
|
|
btn.remove_style_class_name('aerospike-tab-active');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Position and size the tab bar at the given screen rect.
|
|
*/
|
|
setPosition(rect: Rect): void {
|
|
this._bar.set_position(rect.x, rect.y);
|
|
this._bar.set_size(rect.width, rect.height);
|
|
}
|
|
|
|
show(): void {
|
|
if (this._visible) return;
|
|
this._visible = true;
|
|
Main.layoutManager.uiGroup.add_child(this._bar);
|
|
this._bar.show();
|
|
Logger.log("TabBar shown");
|
|
}
|
|
|
|
hide(): void {
|
|
if (!this._visible) return;
|
|
this._visible = false;
|
|
this._bar.hide();
|
|
if (this._bar.get_parent()) {
|
|
Main.layoutManager.uiGroup.remove_child(this._bar);
|
|
}
|
|
Logger.log("TabBar hidden");
|
|
}
|
|
|
|
destroy(): void {
|
|
this.hide();
|
|
this._bar.destroy_all_children();
|
|
this._buttons = [];
|
|
this._bar.destroy();
|
|
Logger.log("TabBar destroyed");
|
|
}
|
|
|
|
isVisible(): boolean {
|
|
return this._visible;
|
|
}
|
|
}
|