mirror of
https://github.com/zoriya/ags.git
synced 2025-12-06 00:06:10 +00:00
refactor how widgets are subclassed (#153)
* rework widget subclassing #121 * remove Utils.connect, add Widget.bind, Widget.connectTo * add: types for on_event fields * bump version to 1.5.1 * export service classes and named instance constants * add starter config example
This commit is contained in:
27
example/starter-config/config.js
Normal file
27
example/starter-config/config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Variable } from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import { Widget } from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
||||
const time = new Variable('', {
|
||||
poll: [1000, 'date'],
|
||||
});
|
||||
|
||||
const Bar = (/** @type {number} */ monitor) => Widget.Window({
|
||||
monitor,
|
||||
name: `bar${monitor}`,
|
||||
anchor: ['top', 'left', 'right'],
|
||||
exclusive: true,
|
||||
child: Widget.CenterBox({
|
||||
start_widget: Widget.Label({
|
||||
hpack: 'center',
|
||||
label: 'Welcome to AGS!',
|
||||
}),
|
||||
end_widget: Widget.Label({
|
||||
hpack: 'center',
|
||||
binds: [['label', time]],
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
export default {
|
||||
windows: [Bar(0)]
|
||||
}
|
||||
21
example/starter-config/package.json
Normal file
21
example/starter-config/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "ags-config",
|
||||
"version": "1.0.0",
|
||||
"description": "AGS starter configuration",
|
||||
"main": "config.js",
|
||||
"author": "Aylur",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Aylur/ags.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.0",
|
||||
"@girs/gobject-2.0": "^2.76.1-3.2.3",
|
||||
"@girs/gtk-3.0": "^3.24.39-3.2.2",
|
||||
"@girs/gvc-1.0": "^1.0.0-3.1.0",
|
||||
"@girs/nm-1.0": "^1.43.1-3.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"eslint": "^8.44.0"
|
||||
}
|
||||
}
|
||||
85
example/starter-config/setup.sh
Executable file
85
example/starter-config/setup.sh
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
dir="${1:-$HOME/.config/ags}"
|
||||
|
||||
mkdir -p /tmp/ags-config
|
||||
cd /tmp/ags-config
|
||||
|
||||
# clone
|
||||
echo "cloning ags"
|
||||
git clone https://github.com/Aylur/ags.git
|
||||
cd ags
|
||||
npm i
|
||||
|
||||
# generate
|
||||
echo "generating types..."
|
||||
tsc -d --declarationDir dts --emitDeclarationOnly
|
||||
|
||||
# fix paths
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/Gtk?version=3.0/node_modules\/@girs\/gtk-3.0\/gtk-3.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/GObject/node_modules\/@girs\/gobject-2.0\/gobject-2.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/Gio/node_modules\/@girs\/gio-2.0\/gio-2.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/GLib/node_modules\/@girs\/glib-2.0\/glib-2.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/GdkPixbuf/node_modules\/@girs\/gdkpixbuf-2.0\/gdkpixbuf-2.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/Gdk/node_modules\/@girs\/gdk-2.0\/gdk-2.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/Gvc/node_modules\/@girs\/gvc-1.0\/gvc-1.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/NM/node_modules\/@girs\/nm-1.0\/nm-1.0/g'
|
||||
find ./dts -type f | xargs sed -i 's/gi:\/\/DbusmenuGtk3/node_modules\/@girs\/dbusmenugtk3-0.4\/dbusmenugtk3-0.4/g'
|
||||
|
||||
# move
|
||||
mv dts $dir/types
|
||||
echo "types moved to $dir/types"
|
||||
|
||||
# gen ags.d.ts
|
||||
function mod {
|
||||
echo "declare module '$1' {
|
||||
const exports: typeof import('$2')
|
||||
export = exports
|
||||
}"
|
||||
}
|
||||
|
||||
function resource {
|
||||
mod "resource:///com/github/Aylur/ags/$1.js" "./$1"
|
||||
}
|
||||
|
||||
function gi {
|
||||
mod "gi://$1" "node_modules/@girs/$2/$2"
|
||||
}
|
||||
|
||||
dts="$dir/types/ags.d.ts"
|
||||
|
||||
echo "
|
||||
declare function print(...args: any[]): void;
|
||||
|
||||
declare module console {
|
||||
export function error(obj: object, others?: object[]): void;
|
||||
export function error(msg: string, subsitutions?: any[]): void;
|
||||
export function log(obj: object, others?: object[]): void;
|
||||
export function log(msg: string, subsitutions?: any[]): void;
|
||||
export function warn(obj: object, others?: object[]): void;
|
||||
export function warn(msg: string, subsitutions?: any[]): void;
|
||||
}
|
||||
" >$dts
|
||||
|
||||
for file in ./src/*.ts; do
|
||||
f=$(basename -s .ts $file)
|
||||
if [[ "$f" != "main" && "$f" != "client" ]]; then
|
||||
resource "$(basename -s .ts $file)" >>$dts
|
||||
fi
|
||||
done
|
||||
|
||||
for file in ./src/service/*.ts; do
|
||||
resource "service/$(basename -s .ts $file)" >>$dts
|
||||
done
|
||||
|
||||
for file in ./src/widgets/*.ts; do
|
||||
resource "widgets/$(basename -s .ts $file)" >>$dts
|
||||
done
|
||||
|
||||
# remove tmp
|
||||
rm -rf /tmp/ags-config
|
||||
|
||||
# npm i
|
||||
echo "npm install @girs"
|
||||
cd $dir
|
||||
npm i
|
||||
20
example/starter-config/tsconfig.json
Normal file
20
example/starter-config/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": [
|
||||
"ES2022"
|
||||
],
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./types/ags.d.ts",
|
||||
"./node_modules/@girs"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
project('ags', 'c',
|
||||
version: '1.5.0',
|
||||
version: '1.5.1',
|
||||
meson_version: '>= 0.62.0',
|
||||
license: ['GPL-3.0-or-later'],
|
||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||
|
||||
@@ -33,7 +33,7 @@ let
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "ags";
|
||||
version = "1.5.0";
|
||||
version = "1.5.1";
|
||||
|
||||
src = buildNpmPackage {
|
||||
name = "ags";
|
||||
@@ -41,7 +41,7 @@ stdenv.mkDerivation {
|
||||
|
||||
dontBuild = true;
|
||||
|
||||
npmDepsHash = "sha256-+Hg4wEnJrMcs0m0hosDF8+UbhKNGSIcl5NcvAsM6U2Q=";
|
||||
npmDepsHash = "sha256-LSx7cvtQffAMA4jHGUmuvhnUOY2PAu0VGW8OHHd67WY=";
|
||||
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ags",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ags",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"license": "GPL",
|
||||
"dependencies": {
|
||||
"typescript": "^5.1.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ags",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"description": "description",
|
||||
"main": "src/main.ts",
|
||||
"repository": "",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CACHE_DIR, ensureDirectory, readFile, writeFile } from '../utils.js';
|
||||
const APPS_CACHE_DIR = `${CACHE_DIR}/apps`;
|
||||
const CACHE_FILE = APPS_CACHE_DIR + '/apps_frequency.json';
|
||||
|
||||
class Application extends Service {
|
||||
export class Application extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'app': ['jsobject'],
|
||||
@@ -71,7 +71,7 @@ class Application extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class Applications extends Service {
|
||||
export class Applications extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'list': ['jsobject'],
|
||||
@@ -134,4 +134,5 @@ class Applications extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
export default new Applications();
|
||||
export const applications = new Applications;
|
||||
export default applications;
|
||||
|
||||
@@ -11,7 +11,7 @@ const _MIXER_CONTROL_STATE = {
|
||||
[Gvc.MixerControlState.FAILED]: 'failed',
|
||||
};
|
||||
|
||||
class Stream extends Service {
|
||||
export class Stream extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'closed': [],
|
||||
@@ -67,7 +67,7 @@ class Stream extends Service {
|
||||
}
|
||||
|
||||
get volume() {
|
||||
const max = audioService.control.get_vol_max_norm();
|
||||
const max = audio.control.get_vol_max_norm();
|
||||
return this._stream.volume / max;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class Stream extends Service {
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
|
||||
const max = audioService.control.get_vol_max_norm();
|
||||
const max = audio.control.get_vol_max_norm();
|
||||
this._stream.set_volume(value * max);
|
||||
this._stream.push_volume();
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class Stream extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class Audio extends Service {
|
||||
export class Audio extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'speaker-changed': [],
|
||||
@@ -225,5 +225,5 @@ class Audio extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
const audioService = new Audio();
|
||||
export default audioService;
|
||||
const audio = new Audio;
|
||||
export default audio;
|
||||
|
||||
@@ -12,7 +12,7 @@ const DeviceState = {
|
||||
FULLY_CHARGED: 4,
|
||||
};
|
||||
|
||||
class Battery extends Service {
|
||||
export class Battery extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'closed': [],
|
||||
@@ -103,4 +103,5 @@ class Battery extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
export default new Battery();
|
||||
export const battery = new Battery;
|
||||
export default battery;
|
||||
|
||||
@@ -13,7 +13,7 @@ const _ADAPTER_STATE = {
|
||||
[GnomeBluetooth.AdapterState.OFF]: 'off',
|
||||
};
|
||||
|
||||
class BluetoothDevice extends Service {
|
||||
export class BluetoothDevice extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'address': ['string'],
|
||||
@@ -78,7 +78,7 @@ class BluetoothDevice extends Service {
|
||||
|
||||
setConnection(connect: boolean) {
|
||||
this._connecting = true;
|
||||
bluetoothService.connectDevice(this, connect, () => {
|
||||
bluetooth.connectDevice(this, connect, () => {
|
||||
this._connecting = false;
|
||||
this.changed('connecting');
|
||||
});
|
||||
@@ -86,7 +86,7 @@ class BluetoothDevice extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class Bluetooth extends Service {
|
||||
export class Bluetooth extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'devices': ['jsobject'],
|
||||
@@ -198,5 +198,5 @@ class Bluetooth extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
const bluetoothService = new Bluetooth();
|
||||
export default bluetoothService;
|
||||
export const bluetooth = new Bluetooth;
|
||||
export default bluetooth;
|
||||
|
||||
@@ -5,14 +5,14 @@ import { execAsync } from '../utils.js';
|
||||
|
||||
const HIS = GLib.getenv('HYPRLAND_INSTANCE_SIGNATURE');
|
||||
|
||||
class Active extends Service {
|
||||
export class Active extends Service {
|
||||
updateProperty(prop: string, value: unknown): void {
|
||||
super.updateProperty(prop, value);
|
||||
this.emit('changed');
|
||||
}
|
||||
}
|
||||
|
||||
class ActiveClient extends Active {
|
||||
export class ActiveClient extends Active {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'address': ['string'],
|
||||
@@ -30,7 +30,7 @@ class ActiveClient extends Active {
|
||||
get class() { return this._class; }
|
||||
}
|
||||
|
||||
class ActiveWorkspace extends Active {
|
||||
export class ActiveWorkspace extends Active {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'id': ['int'],
|
||||
@@ -45,7 +45,7 @@ class ActiveWorkspace extends Active {
|
||||
get name() { return this._name; }
|
||||
}
|
||||
|
||||
class Actives extends Service {
|
||||
export class Actives extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'client': ['jsobject'],
|
||||
@@ -76,7 +76,7 @@ class Actives extends Service {
|
||||
get workspace() { return this._workspace; }
|
||||
}
|
||||
|
||||
class Hyprland extends Service {
|
||||
export class Hyprland extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'urgent-window': ['string'],
|
||||
@@ -312,4 +312,5 @@ class Hyprland extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
export default new Hyprland();
|
||||
export const hyprland = new Hyprland;
|
||||
export default hyprland;
|
||||
|
||||
@@ -26,7 +26,7 @@ type MprisMetadata = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
class MprisPlayer extends Service {
|
||||
export class MprisPlayer extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'closed': [],
|
||||
@@ -266,7 +266,7 @@ class MprisPlayer extends Service {
|
||||
}
|
||||
|
||||
type Players = Map<string, MprisPlayer>;
|
||||
class Mpris extends Service {
|
||||
export class Mpris extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'player-changed': ['string'],
|
||||
@@ -349,4 +349,5 @@ class Mpris extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
export default new Mpris();
|
||||
export const mpris = new Mpris;
|
||||
export default mpris;
|
||||
|
||||
@@ -57,7 +57,7 @@ const DEVICE = (device: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
class Wifi extends Service {
|
||||
export class Wifi extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'enabled': ['boolean', 'rw'],
|
||||
@@ -172,7 +172,7 @@ class Wifi extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class Wired extends Service {
|
||||
export class Wired extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'speed': ['int'],
|
||||
@@ -206,14 +206,14 @@ class Wired extends Service {
|
||||
if (this.internet === 'connected')
|
||||
return 'network-wired-symbolic';
|
||||
|
||||
if (networkService.connectivity !== 'full')
|
||||
if (network.connectivity !== 'full')
|
||||
return 'network-wired-no-route-symbolic';
|
||||
|
||||
return 'network-wired-disconnected-symbolic';
|
||||
}
|
||||
}
|
||||
|
||||
class Network extends Service {
|
||||
export class Network extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
'wifi': ['jsobject'],
|
||||
@@ -286,5 +286,5 @@ class Network extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
const networkService = new Network();
|
||||
export default networkService;
|
||||
const network = new Network;
|
||||
export default network;
|
||||
|
||||
@@ -46,7 +46,7 @@ const _URGENCY = (urgency?: number) => {
|
||||
}
|
||||
};
|
||||
|
||||
class Notification extends Service {
|
||||
export class Notification extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'dismissed': [],
|
||||
@@ -196,7 +196,7 @@ class Notification extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class Notifications extends Service {
|
||||
export class Notifications extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'dismissed': ['int'],
|
||||
@@ -372,5 +372,5 @@ class Notifications extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new Notifications();
|
||||
export const notifications = new Notifications;
|
||||
export default notifications;
|
||||
|
||||
@@ -7,13 +7,18 @@ import DbusmenuGtk3 from 'gi://DbusmenuGtk3';
|
||||
import Service from '../service.js';
|
||||
import { StatusNotifierItemProxy } from '../dbus/types.js';
|
||||
import { bulkConnect, loadInterfaceXML } from '../utils.js';
|
||||
import Widget from '../widget.js';
|
||||
import { subclass } from '../widget.js';
|
||||
|
||||
const StatusNotifierWatcherIFace = loadInterfaceXML('org.kde.StatusNotifierWatcher')!;
|
||||
const StatusNotifierItemIFace = loadInterfaceXML('org.kde.StatusNotifierItem')!;
|
||||
const StatusNotifierItemProxy =
|
||||
Gio.DBusProxy.makeProxyWrapper(StatusNotifierItemIFace) as unknown as StatusNotifierItemProxy;
|
||||
|
||||
const DbusmenuGtk3Menu = subclass<
|
||||
typeof DbusmenuGtk3.Menu,
|
||||
DbusmenuGtk3.Menu.ConstructorProperties
|
||||
>(DbusmenuGtk3.Menu);
|
||||
|
||||
export class TrayItem extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
@@ -116,8 +121,7 @@ export class TrayItem extends Service {
|
||||
|
||||
private _itemProxyAcquired(proxy: StatusNotifierItemProxy) {
|
||||
if (proxy.Menu) {
|
||||
const menu = Widget({
|
||||
type: DbusmenuGtk3.Menu,
|
||||
const menu = DbusmenuGtk3Menu({
|
||||
dbus_name: proxy.g_name_owner,
|
||||
dbus_object: proxy.Menu,
|
||||
});
|
||||
@@ -205,7 +209,7 @@ export class TrayItem extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class SystemTray extends Service {
|
||||
export class SystemTray extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'added': ['string'],
|
||||
@@ -286,5 +290,5 @@ class SystemTray extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new SystemTray();
|
||||
export const systemTray = new SystemTray;
|
||||
export default systemTray;
|
||||
|
||||
58
src/utils.ts
58
src/utils.ts
@@ -2,10 +2,6 @@ import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import GObject from 'gi://GObject';
|
||||
import Service from './service.js';
|
||||
import { App } from './app.js';
|
||||
import { Variable } from './variable.js';
|
||||
import { type Command } from './widgets/widget.js';
|
||||
|
||||
export const USER = GLib.get_user_name();
|
||||
export const CACHE_DIR = `${GLib.get_user_cache_dir()}/${pkg.name.split('.').pop()}`;
|
||||
@@ -25,7 +21,7 @@ export function readFileAsync(path: string): Promise<string> {
|
||||
const [success, bytes] = file.load_contents_finish(res);
|
||||
return success
|
||||
? resolve(new TextDecoder().decode(bytes))
|
||||
: reject(new Error(
|
||||
: reject(Error(
|
||||
`reading file ${path} was unsuccessful`));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
@@ -91,33 +87,6 @@ export function bulkDisconnect(service: GObject.Object, ids: number[]) {
|
||||
service.disconnect(id);
|
||||
}
|
||||
|
||||
export function connect(
|
||||
obj: GObject.Object,
|
||||
widget: Gtk.Widget,
|
||||
callback: (widget: Gtk.Widget, ...args: unknown[]) => void,
|
||||
event?: string,
|
||||
) {
|
||||
if (!(obj instanceof Service || obj instanceof App || obj instanceof Variable) && !event)
|
||||
return console.error(new Error('missing signal for connection'));
|
||||
|
||||
const bind = obj.connect(event as string,
|
||||
(_s, ...args: unknown[]) => callback(widget, ...args));
|
||||
|
||||
const w = Object.assign(widget, { _destroyed: false });
|
||||
|
||||
w.connect('destroy', () => {
|
||||
w._destroyed = true;
|
||||
obj.disconnect(bind);
|
||||
});
|
||||
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||
if (!w._destroyed)
|
||||
callback(w);
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
export function interval(
|
||||
interval: number,
|
||||
callback: () => void,
|
||||
@@ -141,29 +110,6 @@ export function timeout(ms: number, callback: () => void) {
|
||||
});
|
||||
}
|
||||
|
||||
export function runCmd(
|
||||
cmd: Command,
|
||||
...args: unknown[]
|
||||
) {
|
||||
if (typeof cmd !== 'string' && typeof cmd !== 'function') {
|
||||
console.error('Command has to be string or function');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cmd)
|
||||
return false;
|
||||
|
||||
if (typeof cmd === 'string') {
|
||||
GLib.spawn_command_line_async(cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof cmd === 'function')
|
||||
return cmd(...args);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function lookUpIcon(name?: string, size = 16) {
|
||||
if (!name)
|
||||
return null;
|
||||
@@ -243,7 +189,7 @@ export function subprocess(
|
||||
|
||||
const pipe = proc.get_stdout_pipe();
|
||||
if (!pipe) {
|
||||
onError(new Error(`subprocess ${cmd} stdout pipe is null`));
|
||||
onError(Error(`subprocess ${cmd} stdout pipe is null`));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ import GLib from 'gi://GLib';
|
||||
import { execAsync, interval, subprocess } from './utils.js';
|
||||
|
||||
type Listen<T> =
|
||||
[string[] | string, (out: string) => T] |
|
||||
[string[] | string] |
|
||||
string[] |
|
||||
string;
|
||||
| [string[] | string, (out: string) => T]
|
||||
| [string[] | string]
|
||||
| string[]
|
||||
| string;
|
||||
|
||||
type Poll<T> =
|
||||
[number, string[] | string | (() => T)] |
|
||||
[number, string[] | string | (() => T), (out: string) => T];
|
||||
| [number, string[] | string | (() => T)]
|
||||
| [number, string[] | string | (() => T), (out: string) => T];
|
||||
|
||||
interface Options<T> {
|
||||
poll?: Poll<T>
|
||||
|
||||
110
src/widget.ts
110
src/widget.ts
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable max-len */
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import AgsWidget from './widgets/widget.js';
|
||||
import GObject from 'gi://GObject?version=2.0';
|
||||
import AgsWidget, { type BaseProps } from './widgets/widget.js';
|
||||
import AgsBox from './widgets/box.js';
|
||||
import AgsCenterBox from './widgets/centerbox.js';
|
||||
import AgsEventBox from './widgets/eventbox.js';
|
||||
@@ -17,24 +19,27 @@ import { AgsMenu, AgsMenuItem } from './widgets/menu.js';
|
||||
import AgsWindow from './widgets/window.js';
|
||||
import AgsCircularProgress from './widgets/circularprogress.js';
|
||||
|
||||
// @ts-expect-error margin override
|
||||
export const Window = AgsWidget(AgsWindow);
|
||||
export const Box = AgsWidget(AgsBox);
|
||||
export const Button = AgsWidget(AgsButton);
|
||||
export const CenterBox = AgsWidget(AgsCenterBox);
|
||||
export const CircularProgress = AgsWidget(AgsCircularProgress);
|
||||
export const Entry = AgsWidget(AgsEntry);
|
||||
export const EventBox = AgsWidget(AgsEventBox);
|
||||
export const Icon = AgsWidget(AgsIcon);
|
||||
export const Label = AgsWidget(AgsLabel);
|
||||
export const Menu = AgsWidget(AgsMenu);
|
||||
export const MenuItem = AgsWidget(AgsMenuItem);
|
||||
export const Overlay = AgsWidget(AgsOverlay);
|
||||
export const ProgressBar = AgsWidget(AgsProgressBar);
|
||||
export const Revealer = AgsWidget(AgsRevealer);
|
||||
export const Scrollable = AgsWidget(AgsScrollable);
|
||||
export const Slider = AgsWidget(AgsSlider);
|
||||
export const Stack = AgsWidget(AgsStack);
|
||||
function createCtor<T extends typeof Gtk.Widget>(Widget: T) {
|
||||
return (...props: ConstructorParameters<T>) => new Widget(...props) as InstanceType<T>;
|
||||
}
|
||||
|
||||
export const Window = createCtor(AgsWindow);
|
||||
export const Box = createCtor(AgsBox);
|
||||
export const Button = createCtor(AgsButton);
|
||||
export const CenterBox = createCtor(AgsCenterBox);
|
||||
export const CircularProgress = createCtor(AgsCircularProgress);
|
||||
export const Entry = createCtor(AgsEntry);
|
||||
export const EventBox = createCtor(AgsEventBox);
|
||||
export const Icon = createCtor(AgsIcon);
|
||||
export const Label = createCtor(AgsLabel);
|
||||
export const Menu = createCtor(AgsMenu);
|
||||
export const MenuItem = createCtor(AgsMenuItem);
|
||||
export const Overlay = createCtor(AgsOverlay);
|
||||
export const ProgressBar = createCtor(AgsProgressBar);
|
||||
export const Revealer = createCtor(AgsRevealer);
|
||||
export const Scrollable = createCtor(AgsScrollable);
|
||||
export const Slider = createCtor(AgsSlider);
|
||||
export const Stack = createCtor(AgsStack);
|
||||
|
||||
const ctors = new Map();
|
||||
export function Widget<
|
||||
@@ -43,12 +48,16 @@ export function Widget<
|
||||
>({ type, ...props }:
|
||||
{ type: T } & Props,
|
||||
) {
|
||||
if (ctors.has(type))
|
||||
return ctors.get(type)(props);
|
||||
console.warn('Calling Widget({ type }) is deprecated. ' +
|
||||
`Use Widget.subclass instead, or open up an issue/PR to include ${type.name} on Widget`);
|
||||
|
||||
const ctor = AgsWidget(type);
|
||||
ctors.set(type, ctor);
|
||||
return ctor(props);
|
||||
if (ctors.has(type))
|
||||
// @ts-expect-error
|
||||
return new ctors.get(type)(props);
|
||||
|
||||
const Ctor = AgsWidget(type);
|
||||
ctors.set(type, Ctor);
|
||||
return new Ctor(props);
|
||||
}
|
||||
|
||||
// so they are still accessible when importing only Widget
|
||||
@@ -70,4 +79,57 @@ Widget.Slider = Slider;
|
||||
Widget.Stack = Stack;
|
||||
Widget.Window = Window;
|
||||
|
||||
export function subclass<T extends typeof Gtk.Widget, Props>(W: T, GTypeName?: string) {
|
||||
class Widget extends AgsWidget(W, `Gtk${W.name}`) {
|
||||
static { GObject.registerClass({ GTypeName: GTypeName || `Ags${W.name}` }, this); }
|
||||
constructor(props: BaseProps<InstanceType<T> & Widget> & Props) {
|
||||
super(props as Gtk.Widget.ConstructorProperties);
|
||||
}
|
||||
}
|
||||
return (props?: BaseProps<InstanceType<T> & Widget> & Props) => new Widget(props) as InstanceType<T> & Widget;
|
||||
}
|
||||
Widget.subclass = subclass;
|
||||
|
||||
export const Calendar = subclass<typeof Gtk.Calendar, Gtk.Calendar.ConstructorProperties>(Gtk.Calendar);
|
||||
Widget.Calendar = Calendar;
|
||||
|
||||
export const Fixed = subclass<typeof Gtk.Fixed, Gtk.Fixed.ConstructorProperties>(Gtk.Fixed);
|
||||
Widget.Fixed = Fixed;
|
||||
|
||||
export const MenuBar = subclass<typeof Gtk.MenuBar, Gtk.MenuBar.ConstructorProperties>(Gtk.MenuBar);
|
||||
Widget.MenuBar = MenuBar;
|
||||
|
||||
export const Switch = subclass<typeof Gtk.Switch, Gtk.Switch.ConstructorProperties>(Gtk.Switch);
|
||||
Widget.Switch = Switch;
|
||||
|
||||
export const ToggleButton = subclass<typeof Gtk.ToggleButton, Gtk.ToggleButton.ConstructorProperties>(Gtk.ToggleButton);
|
||||
Widget.ToggleButton = ToggleButton;
|
||||
|
||||
export const Separator = subclass<typeof Gtk.Separator, Gtk.Separator.ConstructorProperties>(Gtk.Separator);
|
||||
Widget.Separator = Separator;
|
||||
|
||||
export const LevelBar = subclass<typeof Gtk.LevelBar, Gtk.LevelBar.ConstructorProperties>(Gtk.LevelBar);
|
||||
Widget.LevelBar = LevelBar;
|
||||
|
||||
export const DrawingArea = subclass<typeof Gtk.DrawingArea, Gtk.DrawingArea.ConstructorProperties>(Gtk.DrawingArea);
|
||||
Widget.DrawingArea = DrawingArea;
|
||||
|
||||
export const FontButton = subclass<typeof Gtk.FontButton, Gtk.FontButton.ConstructorProperties>(Gtk.FontButton);
|
||||
Widget.FontButton = FontButton;
|
||||
|
||||
export const ColorButton = subclass<typeof Gtk.ColorButton, Gtk.ColorButton.ConstructorProperties>(Gtk.ColorButton);
|
||||
Widget.ColorButton = ColorButton;
|
||||
|
||||
export const FileChooserButton = subclass<typeof Gtk.FileChooserButton, Gtk.FileChooserButton.ConstructorProperties>(Gtk.FileChooserButton);
|
||||
Widget.FileChooserButton = FileChooserButton;
|
||||
|
||||
export const SpinButton = subclass<typeof Gtk.SpinButton, Gtk.SpinButton.ConstructorProperties>(Gtk.SpinButton);
|
||||
Widget.SpinButton = SpinButton;
|
||||
|
||||
export const Spinner = subclass<typeof Gtk.Spinner, Gtk.Spinner.ConstructorProperties>(Gtk.Spinner);
|
||||
Widget.Spinner = Spinner;
|
||||
|
||||
export const FlowBox = subclass<typeof Gtk.FlowBox, Gtk.FlowBox.ConstructorProperties>(Gtk.FlowBox);
|
||||
Widget.FlowBox = FlowBox;
|
||||
|
||||
export default Widget;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface BoxProps extends Gtk.Box.ConstructorProperties {
|
||||
export interface BoxProps<T> extends BaseProps<T>, Gtk.Box.ConstructorProperties {
|
||||
children?: Gtk.Widget[]
|
||||
vertical?: boolean
|
||||
}
|
||||
|
||||
export default class AgsBox extends Gtk.Box {
|
||||
export default class AgsBox extends AgsWidget(Gtk.Box) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsBox',
|
||||
Properties: {
|
||||
'vertical': Service.pspec('vertical', 'boolean', 'rw'),
|
||||
'children': Service.pspec('children', 'jsobject', 'rw'),
|
||||
@@ -17,12 +19,7 @@ export default class AgsBox extends Gtk.Box {
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor({ children, ...rest }: BoxProps = {}) {
|
||||
super(rest);
|
||||
|
||||
if (children)
|
||||
this.children = children;
|
||||
}
|
||||
constructor(props: BoxProps<AgsBox> = {}) { super(props); }
|
||||
|
||||
get children() { return this.get_children(); }
|
||||
set children(children: Gtk.Widget[]) {
|
||||
|
||||
@@ -1,111 +1,161 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Gdk from 'gi://Gdk?version=3.0';
|
||||
import { runCmd } from '../utils.js';
|
||||
import { type Command } from './widget.js';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface ButtonProps extends Gtk.Button.ConstructorProperties {
|
||||
onClicked?: Command
|
||||
onPrimaryClick?: Command
|
||||
onSecondaryClick?: Command
|
||||
onMiddleClick?: Command
|
||||
onPrimaryClickRelease?: Command
|
||||
onSecondaryClickRelease?: Command
|
||||
onMiddleClickRelease?: Command
|
||||
onHover?: Command
|
||||
onHoverLost?: Command
|
||||
onScrollUp?: Command
|
||||
onScrollDown?: Command
|
||||
type EventHandler = (self: AgsButton, event: Gdk.Event) => boolean | unknown;
|
||||
|
||||
export interface ButtonProps extends BaseProps<AgsButton>, Gtk.Button.ConstructorProperties {
|
||||
on_clicked?: (self: AgsButton) => void
|
||||
|
||||
on_hover?: EventHandler
|
||||
on_hover_lost?: EventHandler
|
||||
|
||||
on_scroll_up?: EventHandler
|
||||
on_scroll_down?: EventHandler
|
||||
|
||||
on_primary_click?: EventHandler
|
||||
on_middle_click?: EventHandler
|
||||
on_secondary_click?: EventHandler
|
||||
|
||||
on_primary_click_release?: EventHandler
|
||||
on_middle_click_release?: EventHandler
|
||||
on_secondary_click_release?: EventHandler
|
||||
}
|
||||
|
||||
export default class AgsButton extends Gtk.Button {
|
||||
static { GObject.registerClass(this); }
|
||||
export default class AgsButton extends AgsWidget(Gtk.Button) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsButton',
|
||||
Properties: {
|
||||
'on-clicked': Service.pspec('on-clicked', 'jsobject', 'rw'),
|
||||
|
||||
onClicked: Command;
|
||||
onPrimaryClick: Command;
|
||||
onSecondaryClick: Command;
|
||||
onMiddleClick: Command;
|
||||
onPrimaryClickRelease: Command;
|
||||
onSecondaryClickRelease: Command;
|
||||
onMiddleClickRelease: Command;
|
||||
onHover: Command;
|
||||
onHoverLost: Command;
|
||||
onScrollUp: Command;
|
||||
onScrollDown: Command;
|
||||
'on-hover':
|
||||
Service.pspec('on-hover', 'jsobject', 'rw'),
|
||||
'on-hover-lost':
|
||||
Service.pspec('on-hover-lost', 'jsobject', 'rw'),
|
||||
|
||||
constructor({
|
||||
onClicked = '',
|
||||
onPrimaryClick = '',
|
||||
onSecondaryClick = '',
|
||||
onMiddleClick = '',
|
||||
onPrimaryClickRelease = '',
|
||||
onSecondaryClickRelease = '',
|
||||
onMiddleClickRelease = '',
|
||||
onHover = '',
|
||||
onHoverLost = '',
|
||||
onScrollUp = '',
|
||||
onScrollDown = '',
|
||||
...rest
|
||||
}: ButtonProps = {}) {
|
||||
super(rest);
|
||||
'on-scroll-up':
|
||||
Service.pspec('on-scroll-up', 'jsobject', 'rw'),
|
||||
'on-scroll-down':
|
||||
Service.pspec('on-scroll-down', 'jsobject', 'rw'),
|
||||
|
||||
'on-primary-click':
|
||||
Service.pspec('on-primary-click', 'jsobject', 'rw'),
|
||||
'on-secondary-click':
|
||||
Service.pspec('on-secondary-click', 'jsobject', 'rw'),
|
||||
'on-middle-click':
|
||||
Service.pspec('on-middle-click', 'jsobject', 'rw'),
|
||||
|
||||
'on-primary-click-release':
|
||||
Service.pspec('on-primary-click-release', 'jsobject', 'rw'),
|
||||
'on-secondary-click-release':
|
||||
Service.pspec('on-secondary-click-release', 'jsobject', 'rw'),
|
||||
'on-middle-click-release':
|
||||
Service.pspec('on-middle-click-release', 'jsobject', 'rw'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(props: ButtonProps = {}) {
|
||||
super(props);
|
||||
this.add_events(Gdk.EventMask.SCROLL_MASK);
|
||||
this.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK);
|
||||
|
||||
this.onClicked = onClicked;
|
||||
this.onPrimaryClick = onPrimaryClick;
|
||||
this.onSecondaryClick = onSecondaryClick;
|
||||
this.onMiddleClick = onMiddleClick;
|
||||
this.onPrimaryClickRelease = onPrimaryClickRelease;
|
||||
this.onSecondaryClickRelease = onSecondaryClickRelease;
|
||||
this.onMiddleClickRelease = onMiddleClickRelease;
|
||||
this.onHover = onHover;
|
||||
this.onHoverLost = onHoverLost;
|
||||
this.onScrollUp = onScrollUp;
|
||||
this.onScrollDown = onScrollDown;
|
||||
this.connect('clicked', () => this.on_clicked?.(this));
|
||||
|
||||
this.connect('clicked', (...args) => runCmd(this.onClicked, ...args));
|
||||
|
||||
this.connect('enter-notify-event', (btn, event) => {
|
||||
return runCmd(this.onHover, btn, event);
|
||||
this.connect('enter-notify-event', (_, event: Gdk.Event) => {
|
||||
return this.on_hover?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('leave-notify-event', (btn, event) => {
|
||||
return runCmd(this.onHoverLost, btn, event);
|
||||
this.connect('leave-notify-event', (_, event: Gdk.Event) => {
|
||||
return this.on_hover_lost?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('button-press-event', (_, event: Gdk.Event) => {
|
||||
if (this.onPrimaryClick &&
|
||||
event.get_button()[1] === Gdk.BUTTON_PRIMARY)
|
||||
return runCmd(this.onPrimaryClick, this, event);
|
||||
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY)
|
||||
return this.on_primary_click?.(this, event);
|
||||
|
||||
else if (this.onSecondaryClick &&
|
||||
event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
return runCmd(this.onSecondaryClick, this, event);
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE)
|
||||
return this.on_middle_click?.(this, event);
|
||||
|
||||
else if (this.onMiddleClick &&
|
||||
event.get_button()[1] === Gdk.BUTTON_MIDDLE)
|
||||
return runCmd(this.onMiddleClick, this, event);
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
return this.on_secondary_click?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('button-release-event', (_, event: Gdk.Event) => {
|
||||
if (this.onPrimaryClickRelease &&
|
||||
event.get_button()[1] === Gdk.BUTTON_PRIMARY)
|
||||
return runCmd(this.onPrimaryClickRelease, this, event);
|
||||
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY)
|
||||
return this.on_primary_click_release?.(this, event);
|
||||
|
||||
else if (this.onSecondaryClickRelease &&
|
||||
event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
return runCmd(this.onSecondaryClickRelease, this, event);
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE)
|
||||
return this.on_middle_click_release?.(this, event);
|
||||
|
||||
else if (this.onMiddleClickRelease &&
|
||||
event.get_button()[1] === Gdk.BUTTON_MIDDLE)
|
||||
return runCmd(this.onMiddleClickRelease, this, event);
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
return this.on_secondary_click_release?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('scroll-event', (box, event) => {
|
||||
this.connect('scroll-event', (_, event: Gdk.Event) => {
|
||||
if (event.get_scroll_deltas()[2] < 0)
|
||||
return runCmd(this.onScrollUp, box, event);
|
||||
return this.on_scroll_up?.(this, event);
|
||||
|
||||
else if (event.get_scroll_deltas()[2] > 0)
|
||||
return runCmd(this.onScrollDown, box, event);
|
||||
return this.on_scroll_down?.(this, event);
|
||||
});
|
||||
}
|
||||
|
||||
get on_clicked() { return this._get('on-clicked'); }
|
||||
set on_clicked(callback: ButtonProps['on_clicked']) {
|
||||
this._set('on-clicked', callback);
|
||||
}
|
||||
|
||||
get on_hover() { return this._get('on-hover'); }
|
||||
set on_hover(callback: EventHandler) {
|
||||
this._set('on-hover', callback);
|
||||
}
|
||||
|
||||
get on_hover_lost() { return this._get('on-hover-lost'); }
|
||||
set on_hover_lost(callback: EventHandler) {
|
||||
this._set('on-hover-lost', callback);
|
||||
}
|
||||
|
||||
get on_scroll_up() { return this._get('on-scroll-up'); }
|
||||
set on_scroll_up(callback: EventHandler) {
|
||||
this._set('on-scroll-up', callback);
|
||||
}
|
||||
|
||||
get on_scroll_down() { return this._get('on-scroll-down'); }
|
||||
set on_scroll_down(callback: EventHandler) {
|
||||
this._set('on-scroll-down', callback);
|
||||
}
|
||||
|
||||
get on_primary_click() { return this._get('on-primary-click'); }
|
||||
set on_primary_click(callback: EventHandler) {
|
||||
this._set('on-primary-click', callback);
|
||||
}
|
||||
|
||||
get on_middle_click() { return this._get('on-middle-click'); }
|
||||
set on_middle_click(callback: EventHandler) {
|
||||
this._set('on-middle-click', callback);
|
||||
}
|
||||
|
||||
get on_secondary_click() { return this._get('on-secondary-click'); }
|
||||
set on_secondary_click(callback: EventHandler) {
|
||||
this._set('on-secondary-click', callback);
|
||||
}
|
||||
|
||||
get on_primary_click_release() { return this._get('on-primary-click-release'); }
|
||||
set on_primary_click_release(callback: EventHandler) {
|
||||
this._set('on-primary-click-release', callback);
|
||||
}
|
||||
|
||||
get on_middle_click_release() { return this._get('on-middle-click-release'); }
|
||||
set on_middle_click_release(callback: EventHandler) {
|
||||
this._set('on-middle-click-release', callback);
|
||||
}
|
||||
|
||||
get on_secondary_click_release() { return this._get('on-secondary-click-release'); }
|
||||
set on_secondary_click_release(callback: EventHandler) {
|
||||
this._set('on-secondary-click-release', callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import AgsBox from './box.js';
|
||||
import AgsBox, { type BoxProps } from './box.js';
|
||||
|
||||
export interface CenterBoxProps extends Gtk.Box.ConstructorProperties {
|
||||
children?: Gtk.Widget[]
|
||||
export interface CenterBoxProps extends BoxProps<AgsCenterBox> {
|
||||
start_widget?: Gtk.Widget
|
||||
center_widget?: Gtk.Widget
|
||||
end_widget?: Gtk.Widget
|
||||
@@ -12,6 +11,7 @@ export interface CenterBoxProps extends Gtk.Box.ConstructorProperties {
|
||||
export default class AgsCenterBox extends AgsBox {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsCenterBox',
|
||||
Properties: {
|
||||
'start-widget': GObject.ParamSpec.object(
|
||||
'start-widget', 'Start Widget', 'Start Widget',
|
||||
@@ -32,6 +32,8 @@ export default class AgsCenterBox extends AgsBox {
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(props: CenterBoxProps = {}) { super(props as BoxProps<AgsBox>); }
|
||||
|
||||
set children(children: Gtk.Widget[]) {
|
||||
const newChildren = children || [];
|
||||
|
||||
@@ -48,37 +50,31 @@ export default class AgsCenterBox extends AgsBox {
|
||||
this.end_widget = children[2];
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get start_widget() { return this._startWidget || null; }
|
||||
get start_widget() { return this._get('start-widget') || null; }
|
||||
set start_widget(child: Gtk.Widget | null) {
|
||||
if (this.start_widget)
|
||||
this.start_widget.destroy();
|
||||
|
||||
// @ts-expect-error
|
||||
this._startWidget = child;
|
||||
this._set('start-widget', child);
|
||||
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
this.pack_start(child, true, true, 0);
|
||||
this.notify('start-widget');
|
||||
this.show_all();
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get end_widget() { return this._endWidget || null; }
|
||||
get end_widget() { return this._get('end-widget') || null; }
|
||||
set end_widget(child: Gtk.Widget | null) {
|
||||
if (this.end_widget)
|
||||
this.end_widget.destroy();
|
||||
|
||||
// @ts-expect-error
|
||||
this._endWidget = child;
|
||||
this._set('end-widget', child);
|
||||
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
this.pack_end(child, true, true, 0);
|
||||
this.notify('end-widget');
|
||||
this.show_all();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
|
||||
// type from gi-types is wrong
|
||||
interface Context {
|
||||
setSourceRGBA: (r: number, g: number, b: number, a: number) => void
|
||||
arc: (x: number, y: number, r: number, a1: number, a2: number) => void
|
||||
@@ -13,16 +13,18 @@ interface Context {
|
||||
$dispose: () => void
|
||||
}
|
||||
|
||||
export interface CircularProgressProps extends Gtk.Bin {
|
||||
export interface CircularProgressProps extends
|
||||
BaseProps<AgsCircularProgress>, Gtk.Bin.ConstructorProperties {
|
||||
rounded?: boolean
|
||||
value?: number
|
||||
inverted?: boolean
|
||||
start_at?: number
|
||||
}
|
||||
|
||||
export default class AgsCircularProgress extends Gtk.Bin {
|
||||
export default class AgsCircularProgress extends AgsWidget(Gtk.Bin) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsCircularProgress',
|
||||
CssName: 'circular-progress',
|
||||
Properties: {
|
||||
'start-at': Service.pspec('start-at', 'float', 'rw'),
|
||||
@@ -33,32 +35,27 @@ export default class AgsCircularProgress extends Gtk.Bin {
|
||||
}, this);
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get rounded() { return this._rounded || false; }
|
||||
constructor(props: CircularProgressProps = {}) { super(props); }
|
||||
|
||||
get rounded() { return this._get('rounded') || false; }
|
||||
set rounded(r: boolean) {
|
||||
if (this.rounded === r)
|
||||
return;
|
||||
|
||||
// @ts-expect-error
|
||||
this._rounded = r;
|
||||
this.notify('rounded');
|
||||
this._set('rounded', r);
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get inverted() { return this._inverted || false; }
|
||||
get inverted() { return this._get('inverted') || false; }
|
||||
set inverted(inverted: boolean) {
|
||||
if (this.inverted === inverted)
|
||||
return;
|
||||
|
||||
// @ts-expect-error
|
||||
this._inverted = inverted;
|
||||
this.notify('inverted');
|
||||
this._set('inverted', inverted);
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get start_at() { return this._startAt || 0; }
|
||||
get start_at() { return this._get('start-at') || 0; }
|
||||
set start_at(value: number) {
|
||||
if (this.start_at === value)
|
||||
return;
|
||||
@@ -69,14 +66,11 @@ export default class AgsCircularProgress extends Gtk.Bin {
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
|
||||
// @ts-expect-error
|
||||
this._startAt = value;
|
||||
this.notify('start-at');
|
||||
this._set('start-at', value);
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get value() { return this._value || 0; }
|
||||
get value() { return this._get('value') || 0; }
|
||||
set value(value: number) {
|
||||
if (this.value === value)
|
||||
return;
|
||||
@@ -88,9 +82,7 @@ export default class AgsCircularProgress extends Gtk.Bin {
|
||||
value = 0;
|
||||
|
||||
|
||||
// @ts-expect-error
|
||||
this._value = value;
|
||||
this.notify('value');
|
||||
this._set('value', value);
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import { runCmd } from '../utils.js';
|
||||
import { type Command } from './widget.js';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface EntryProps extends Gtk.Entry.ConstructorProperties {
|
||||
onAccept?: Command
|
||||
onChange?: Command
|
||||
export interface EntryProps extends BaseProps<AgsEntry>, Gtk.Entry.ConstructorProperties {
|
||||
on_accept?: (self: AgsEntry) => void | unknown
|
||||
on_change?: (self: AgsEntry) => void | unknown
|
||||
}
|
||||
|
||||
export default class AgsEntry extends Gtk.Entry {
|
||||
static { GObject.registerClass(this); }
|
||||
export default class AgsEntry extends AgsWidget(Gtk.Entry) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsEntry',
|
||||
Properties: {
|
||||
'on-accept': Service.pspec('on-accept', 'jsobject', 'rw'),
|
||||
'on-change': Service.pspec('on-change', 'jsobject', 'rw'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
onAccept: Command;
|
||||
onChange: Command;
|
||||
constructor(props: EntryProps = {}) {
|
||||
super(props);
|
||||
|
||||
constructor({ onAccept = '', onChange = '', ...rest }: EntryProps = {}) {
|
||||
super(rest);
|
||||
this.connect('activate', () => this.on_accept?.(this));
|
||||
this.connect('notify::text', () => this.on_change?.(this));
|
||||
}
|
||||
|
||||
this.onAccept = onAccept;
|
||||
this.onChange = onChange;
|
||||
get on_accept() { return this._get('on-accept'); }
|
||||
set on_accept(callback: EntryProps['on_accept']) {
|
||||
this._set('on-accept', callback);
|
||||
}
|
||||
|
||||
this.connect('activate', () => {
|
||||
typeof this.onAccept === 'function'
|
||||
? this.onAccept(this)
|
||||
: runCmd(this.onAccept.replace(/\{\}/g, this.text || ''));
|
||||
});
|
||||
|
||||
this.connect('notify::text', ({ text }, event) => {
|
||||
typeof this.onChange === 'function'
|
||||
? this.onChange(this, event)
|
||||
: runCmd(this.onChange.replace(/\{\}/g, text || ''));
|
||||
});
|
||||
get on_change() { return this._get('on-change'); }
|
||||
set on_change(callback: EntryProps['on_change']) {
|
||||
this._set('on-change', callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,103 +1,154 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Gdk from 'gi://Gdk?version=3.0';
|
||||
import { runCmd } from '../utils.js';
|
||||
import { type Command } from './widget.js';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface EventBoxProps extends Gtk.EventBox.ConstructorProperties {
|
||||
onPrimaryClick?: Command
|
||||
onSecondaryClick?: Command
|
||||
onMiddleClick?: Command
|
||||
onPrimaryClickRelease?: Command
|
||||
onSecondaryClickRelease?: Command
|
||||
onMiddleClickRelease?: Command
|
||||
onHover?: Command
|
||||
onHoverLost?: Command
|
||||
onScrollUp?: Command
|
||||
onScrollDown?: Command
|
||||
type EventHandler = (self: AgsEventBox, event: Gdk.Event) => boolean | unknown;
|
||||
|
||||
export interface EventBoxProps extends BaseProps<AgsEventBox>, Gtk.EventBox.ConstructorProperties {
|
||||
on_hover?: EventHandler
|
||||
on_hover_lost?: EventHandler
|
||||
|
||||
on_scroll_up?: EventHandler
|
||||
on_scroll_down?: EventHandler
|
||||
|
||||
on_primary_click?: EventHandler
|
||||
on_middle_click?: EventHandler
|
||||
on_secondary_click?: EventHandler
|
||||
|
||||
on_primary_click_release?: EventHandler
|
||||
on_middle_click_release?: EventHandler
|
||||
on_secondary_click_release?: EventHandler
|
||||
}
|
||||
|
||||
export default class AgsEventBox extends Gtk.EventBox {
|
||||
static { GObject.registerClass(this); }
|
||||
export default class AgsEventBox extends AgsWidget(Gtk.EventBox) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsEventBox',
|
||||
Properties: {
|
||||
'on-hover':
|
||||
Service.pspec('on-hover', 'jsobject', 'rw'),
|
||||
'on-hover-lost':
|
||||
Service.pspec('on-hover-lost', 'jsobject', 'rw'),
|
||||
|
||||
onPrimaryClick: Command;
|
||||
onSecondaryClick: Command;
|
||||
onMiddleClick: Command;
|
||||
onPrimaryClickRelease: Command;
|
||||
onSecondaryClickRelease: Command;
|
||||
onMiddleClickRelease: Command;
|
||||
onHover: Command;
|
||||
onHoverLost: Command;
|
||||
onScrollUp: Command;
|
||||
onScrollDown: Command;
|
||||
'on-scroll-up':
|
||||
Service.pspec('on-scroll-up', 'jsobject', 'rw'),
|
||||
'on-scroll-down':
|
||||
Service.pspec('on-scroll-down', 'jsobject', 'rw'),
|
||||
|
||||
constructor({
|
||||
onPrimaryClick = '',
|
||||
onSecondaryClick = '',
|
||||
onMiddleClick = '',
|
||||
onPrimaryClickRelease = '',
|
||||
onSecondaryClickRelease = '',
|
||||
onMiddleClickRelease = '',
|
||||
onHover = '',
|
||||
onHoverLost = '',
|
||||
onScrollUp = '',
|
||||
onScrollDown = '',
|
||||
...params
|
||||
}: EventBoxProps = {}) {
|
||||
super(params);
|
||||
'on-primary-click':
|
||||
Service.pspec('on-primary-click', 'jsobject', 'rw'),
|
||||
'on-secondary-click':
|
||||
Service.pspec('on-secondary-click', 'jsobject', 'rw'),
|
||||
'on-middle-click':
|
||||
Service.pspec('on-middle-click', 'jsobject', 'rw'),
|
||||
|
||||
'on-primary-click-release':
|
||||
Service.pspec('on-primary-click-release', 'jsobject', 'rw'),
|
||||
'on-secondary-click-release':
|
||||
Service.pspec('on-secondary-click-release', 'jsobject', 'rw'),
|
||||
'on-middle-click-release':
|
||||
Service.pspec('on-middle-click-release', 'jsobject', 'rw'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(props: EventBoxProps = {}) {
|
||||
super(props);
|
||||
this.add_events(Gdk.EventMask.SCROLL_MASK);
|
||||
this.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK);
|
||||
|
||||
this.onPrimaryClick = onPrimaryClick;
|
||||
this.onSecondaryClick = onSecondaryClick;
|
||||
this.onMiddleClick = onMiddleClick;
|
||||
this.onPrimaryClickRelease = onPrimaryClickRelease;
|
||||
this.onSecondaryClickRelease = onSecondaryClickRelease;
|
||||
this.onMiddleClickRelease = onMiddleClickRelease;
|
||||
this.onHover = onHover;
|
||||
this.onHoverLost = onHoverLost;
|
||||
this.onScrollUp = onScrollUp;
|
||||
this.onScrollDown = onScrollDown;
|
||||
|
||||
this.connect('enter-notify-event', (box, event) => {
|
||||
box.set_state_flags(Gtk.StateFlags.PRELIGHT, false);
|
||||
return runCmd(this.onHover, box, event);
|
||||
this.connect('enter-notify-event', (_, event: Gdk.Event) => {
|
||||
this.set_state_flags(Gtk.StateFlags.PRELIGHT, false);
|
||||
return this.on_hover?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('leave-notify-event', (box, event) => {
|
||||
box.unset_state_flags(Gtk.StateFlags.PRELIGHT);
|
||||
return runCmd(this.onHoverLost, box, event);
|
||||
this.connect('leave-notify-event', (_, event: Gdk.Event) => {
|
||||
this.unset_state_flags(Gtk.StateFlags.PRELIGHT);
|
||||
return this.on_hover_lost?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('button-press-event', (box, event) => {
|
||||
box.set_state_flags(Gtk.StateFlags.ACTIVE, false);
|
||||
this.connect('button-press-event', (_, event: Gdk.Event) => {
|
||||
this.set_state_flags(Gtk.StateFlags.ACTIVE, false);
|
||||
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY)
|
||||
return runCmd(this.onPrimaryClick, box, event);
|
||||
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
return runCmd(this.onSecondaryClick, box, event);
|
||||
return this.on_primary_click?.(this, event);
|
||||
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE)
|
||||
return runCmd(this.onMiddleClick, box, event);
|
||||
});
|
||||
|
||||
this.connect('button-release-event', (box, event) => {
|
||||
box.unset_state_flags(Gtk.StateFlags.ACTIVE);
|
||||
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY)
|
||||
return runCmd(this.onPrimaryClickRelease, box, event);
|
||||
return this.on_middle_click?.(this, event);
|
||||
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
return runCmd(this.onSecondaryClickRelease, box, event);
|
||||
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE)
|
||||
return runCmd(this.onMiddleClickRelease, box, event);
|
||||
return this.on_secondary_click?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('scroll-event', (box, event) => {
|
||||
this.connect('button-release-event', (_, event: Gdk.Event) => {
|
||||
this.unset_state_flags(Gtk.StateFlags.ACTIVE);
|
||||
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY)
|
||||
return this.on_primary_click_release?.(this, event);
|
||||
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE)
|
||||
return this.on_middle_click_release?.(this, event);
|
||||
|
||||
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY)
|
||||
return this.on_secondary_click_release?.(this, event);
|
||||
});
|
||||
|
||||
this.connect('scroll-event', (_, event: Gdk.Event) => {
|
||||
if (event.get_scroll_deltas()[2] < 0)
|
||||
return runCmd(this.onScrollUp, box, event);
|
||||
return this.on_scroll_up?.(this, event);
|
||||
|
||||
else if (event.get_scroll_deltas()[2] > 0)
|
||||
return runCmd(this.onScrollDown, box, event);
|
||||
return this.on_scroll_down?.(this, event);
|
||||
});
|
||||
}
|
||||
|
||||
get on_hover() { return this._get('on-hover'); }
|
||||
set on_hover(callback: EventHandler) {
|
||||
this._set('on-hover', callback);
|
||||
}
|
||||
|
||||
get on_hover_lost() { return this._get('on-hover-lost'); }
|
||||
set on_hover_lost(callback: EventHandler) {
|
||||
this._set('on-hover-lost', callback);
|
||||
}
|
||||
|
||||
get on_scroll_up() { return this._get('on-scroll-up'); }
|
||||
set on_scroll_up(callback: EventHandler) {
|
||||
this._set('on-scroll-up', callback);
|
||||
}
|
||||
|
||||
get on_scroll_down() { return this._get('on-scroll-down'); }
|
||||
set on_scroll_down(callback: EventHandler) {
|
||||
this._set('on-scroll-down', callback);
|
||||
}
|
||||
|
||||
get on_primary_click() { return this._get('on-primary-click'); }
|
||||
set on_primary_click(callback: EventHandler) {
|
||||
this._set('on-primary-click', callback);
|
||||
}
|
||||
|
||||
get on_middle_click() { return this._get('on-middle-click'); }
|
||||
set on_middle_click(callback: EventHandler) {
|
||||
this._set('on-middle-click', callback);
|
||||
}
|
||||
|
||||
get on_secondary_click() { return this._get('on-secondary-click'); }
|
||||
set on_secondary_click(callback: EventHandler) {
|
||||
this._set('on-secondary-click', callback);
|
||||
}
|
||||
|
||||
get on_primary_click_release() { return this._get('on-primary-click-release'); }
|
||||
set on_primary_click_release(callback: EventHandler) {
|
||||
this._set('on-primary-click-release', callback);
|
||||
}
|
||||
|
||||
get on_middle_click_release() { return this._get('on-middle-click-release'); }
|
||||
set on_middle_click_release(callback: EventHandler) {
|
||||
this._set('on-middle-click-release', callback);
|
||||
}
|
||||
|
||||
get on_secondary_click_release() { return this._get('on-secondary-click-release'); }
|
||||
set on_secondary_click_release(callback: EventHandler) {
|
||||
this._set('on-secondary-click-release', callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import GLib from 'gi://GLib';
|
||||
@@ -6,80 +7,74 @@ import Gdk from 'gi://Gdk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
import cairo from '@girs/cairo-1.0';
|
||||
|
||||
interface Props extends Gtk.Image.ConstructorProperties {
|
||||
interface Props extends BaseProps<AgsIcon>, Gtk.Image.ConstructorProperties {
|
||||
icon?: string | GdkPixbuf.Pixbuf
|
||||
size?: number
|
||||
}
|
||||
|
||||
export type IconProps = Props | string | GdkPixbuf.Pixbuf | undefined
|
||||
|
||||
export default class AgsIcon extends Gtk.Image {
|
||||
export default class AgsIcon extends AgsWidget(Gtk.Image) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsIcon',
|
||||
Properties: {
|
||||
'icon': Service.pspec('icon', 'jsobject', 'rw'),
|
||||
'type': Service.pspec('type', 'string', 'r'),
|
||||
'size': Service.pspec('size', 'double', 'rw'),
|
||||
'previous-size': Service.pspec('previous-size', 'double', 'r'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(params: IconProps | string | GdkPixbuf.Pixbuf = {}) {
|
||||
const {
|
||||
icon = '',
|
||||
size = 0,
|
||||
...rest
|
||||
} = params as { icon: string | GdkPixbuf.Pixbuf, size: number };
|
||||
super(typeof params === 'string' || params instanceof GdkPixbuf.Pixbuf ? {} : rest);
|
||||
constructor(props: IconProps = {}) {
|
||||
const { icon = '', ...rest } = props as Props;
|
||||
super(typeof props === 'string' || props instanceof GdkPixbuf.Pixbuf ? {} : rest);
|
||||
|
||||
this.size = size;
|
||||
this.icon = typeof params === 'string' || params instanceof GdkPixbuf.Pixbuf
|
||||
? params : icon;
|
||||
// jsobject pspec can't take a string, so we have to set it after constructor
|
||||
this.icon = typeof props === 'string' || props instanceof GdkPixbuf.Pixbuf
|
||||
? props : icon;
|
||||
}
|
||||
|
||||
_size = 0;
|
||||
_previousSize = 0;
|
||||
get size() { return this._size || this._previousSize || 13; }
|
||||
get size() { return this._get('size') || this._get('previous-size') || 0; }
|
||||
set size(size: number) {
|
||||
this._size = size;
|
||||
this.notify('size');
|
||||
this._set('size', size);
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get icon() { return this._icon; }
|
||||
get icon() { return this._get('icon'); }
|
||||
set icon(icon: string | GdkPixbuf.Pixbuf) {
|
||||
if (!icon || this.icon === icon)
|
||||
return;
|
||||
this._set('icon', icon);
|
||||
|
||||
// @ts-expect-error
|
||||
this._icon = icon;
|
||||
this.notify('icon');
|
||||
if (typeof icon === 'string') {
|
||||
if (GLib.file_test(icon, GLib.FileTest.EXISTS)) {
|
||||
// @ts-expect-error
|
||||
this._type = 'file';
|
||||
const pb =
|
||||
GdkPixbuf.Pixbuf.new_from_file_at_size(
|
||||
this.icon as string,
|
||||
this.size * this.scale_factor,
|
||||
this.size * this.scale_factor);
|
||||
this._set('type', 'file');
|
||||
if (this.size === 0)
|
||||
return;
|
||||
|
||||
const pb = GdkPixbuf.Pixbuf.new_from_file_at_size(
|
||||
icon,
|
||||
this.size * this.scale_factor,
|
||||
this.size * this.scale_factor,
|
||||
);
|
||||
const cs = Gdk.cairo_surface_create_from_pixbuf(pb, 0, this.get_window());
|
||||
this.set_from_surface(cs);
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
this._type = 'named';
|
||||
this._set('type', 'named');
|
||||
this.icon_name = icon;
|
||||
this.pixel_size = this.size;
|
||||
}
|
||||
}
|
||||
else if (icon instanceof GdkPixbuf.Pixbuf) {
|
||||
// @ts-expect-error
|
||||
this._type = 'pixbuf';
|
||||
const pb_scaled =
|
||||
icon.scale_simple(
|
||||
this.size * this.scale_factor,
|
||||
this.size * this.scale_factor,
|
||||
GdkPixbuf.InterpType.BILINEAR);
|
||||
this._set('type', 'pixbuf');
|
||||
if (this.size === 0)
|
||||
return;
|
||||
|
||||
const pb_scaled = icon.scale_simple(
|
||||
this.size * this.scale_factor,
|
||||
this.size * this.scale_factor,
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
);
|
||||
if (pb_scaled) {
|
||||
const cs = Gdk.cairo_surface_create_from_pixbuf(pb_scaled, 0, this.get_window());
|
||||
this.set_from_surface(cs);
|
||||
@@ -91,22 +86,21 @@ export default class AgsIcon extends Gtk.Image {
|
||||
}
|
||||
|
||||
vfunc_draw(cr: cairo.Context): boolean {
|
||||
if (this._size > 1)
|
||||
if (this.size > 1)
|
||||
return super.vfunc_draw(cr);
|
||||
|
||||
const size = this.get_style_context()
|
||||
.get_property('font-size', Gtk.StateFlags.NORMAL) as number;
|
||||
|
||||
if (size === this._previousSize)
|
||||
if (size === this._get('previous-size'))
|
||||
return super.vfunc_draw(cr);
|
||||
|
||||
this._previousSize = size;
|
||||
this._set('previous-size', size);
|
||||
|
||||
// @ts-expect-error
|
||||
switch (this._type) {
|
||||
switch (this._get('type')) {
|
||||
case 'file': {
|
||||
const pb = GdkPixbuf.Pixbuf.new_from_file_at_size(
|
||||
this.icon as string,
|
||||
this._get<string>('icon'),
|
||||
size * this.scale_factor,
|
||||
size * this.scale_factor);
|
||||
|
||||
@@ -115,15 +109,15 @@ export default class AgsIcon extends Gtk.Image {
|
||||
break;
|
||||
}
|
||||
case 'pixbuf': {
|
||||
const pb_scaled =
|
||||
(this.icon as GdkPixbuf.Pixbuf).scale_simple(
|
||||
size * this.scale_factor,
|
||||
size * this.scale_factor,
|
||||
GdkPixbuf.InterpType.BILINEAR);
|
||||
|
||||
const pb_scaled = this._get<GdkPixbuf.Pixbuf>('icon').scale_simple(
|
||||
size * this.scale_factor,
|
||||
size * this.scale_factor,
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
);
|
||||
if (pb_scaled) {
|
||||
const cs = Gdk.cairo_surface_create_from_pixbuf(
|
||||
pb_scaled, 0, this.get_window());
|
||||
pb_scaled, 0, this.get_window(),
|
||||
);
|
||||
this.set_from_surface(cs);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import GLib from 'gi://GLib';
|
||||
@@ -10,16 +11,17 @@ const truncates = ['none', 'start', 'middle', 'end'] as const;
|
||||
type Justification = typeof justifications[number];
|
||||
type Truncate = typeof truncates[number];
|
||||
|
||||
interface Props extends Gtk.Label.ConstructorProperties {
|
||||
interface Props extends BaseProps<AgsLabel>, Gtk.Label.ConstructorProperties {
|
||||
justification?: Justification
|
||||
truncate?: Truncate
|
||||
}
|
||||
|
||||
export type LabelProps = Props | string | undefined
|
||||
|
||||
export default class AgsLabel extends Gtk.Label {
|
||||
export default class AgsLabel extends AgsWidget(Gtk.Label) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsLabel',
|
||||
Properties: {
|
||||
'justification': Service.pspec('justification', 'string', 'rw'),
|
||||
'truncate': Service.pspec('truncate', 'string', 'rw'),
|
||||
@@ -27,8 +29,8 @@ export default class AgsLabel extends Gtk.Label {
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(params: LabelProps = {}) {
|
||||
super(typeof params === 'string' ? { label: params } : params);
|
||||
constructor(props: LabelProps = {}) {
|
||||
super(typeof props === 'string' ? { label: props } : props);
|
||||
}
|
||||
|
||||
get label() { return super.label || ''; }
|
||||
|
||||
@@ -1,39 +1,47 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import { runCmd } from '../utils.js';
|
||||
import { type Command } from './widget.js';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface MenuProps extends Gtk.Menu.ConstructorProperties {
|
||||
export interface MenuProps extends BaseProps<AgsMenu>, Gtk.Menu.ConstructorProperties {
|
||||
children?: Gtk.Widget[]
|
||||
onPopup?: Command
|
||||
onMoveScroll?: Command
|
||||
on_popup?: (
|
||||
self: AgsMenu,
|
||||
flipped_rect: any | null,
|
||||
final_rect: any | null,
|
||||
flipped_x: boolean,
|
||||
flipped_y: boolean,
|
||||
) => void | unknown
|
||||
on_move_scroll?: (self: AgsMenu, scroll_type: Gtk.ScrollType) => void | unknown
|
||||
}
|
||||
|
||||
export class AgsMenu extends Gtk.Menu {
|
||||
static { GObject.registerClass(this); }
|
||||
export class AgsMenu extends AgsWidget(Gtk.Menu) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsMenu',
|
||||
Properties: {
|
||||
'children': Service.pspec('children', 'jsobject', 'rw'),
|
||||
'on-popup': Service.pspec('on-popup', 'jsobject', 'rw'),
|
||||
'on-move-scroll': Service.pspec('on-move-scroll', 'jsobject', 'rw'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
onPopup: Command;
|
||||
onMoveScroll: Command;
|
||||
constructor(props: MenuProps = {}) {
|
||||
super(props);
|
||||
|
||||
constructor({
|
||||
children,
|
||||
onPopup = '',
|
||||
onMoveScroll = '',
|
||||
...rest
|
||||
}: MenuProps = {}) {
|
||||
super(rest);
|
||||
this.connect('popped-up', (_, ...args) => this.on_popup?.(this, ...args));
|
||||
this.connect('move-scroll', (_, ...args) => this.on_move_scroll?.(this, ...args));
|
||||
}
|
||||
|
||||
if (children)
|
||||
this.children = children;
|
||||
get on_popup() { return this._get('on-popup'); }
|
||||
set on_popup(callback: MenuProps['on_popup']) {
|
||||
this._set('on-popup', callback);
|
||||
}
|
||||
|
||||
this.onPopup = onPopup;
|
||||
this.onMoveScroll = onMoveScroll;
|
||||
|
||||
this.connect('popped-up', (...args) =>
|
||||
runCmd(this.onPopup, ...args));
|
||||
|
||||
this.connect('move-scroll', (...args) =>
|
||||
runCmd(this.onMoveScroll, ...args));
|
||||
get on_move_scroll() { return this._get('on-move-scroll'); }
|
||||
set on_move_scroll(callback: MenuProps['on_move_scroll']) {
|
||||
this._set('on-move-scroll', callback);
|
||||
}
|
||||
|
||||
get children() { return this.get_children(); }
|
||||
@@ -51,33 +59,45 @@ export class AgsMenu extends Gtk.Menu {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MenuItemProps extends Gtk.Menu.ConstructorProperties {
|
||||
onActivate?: Command
|
||||
onSelect?: Command
|
||||
onDeselect?: Command
|
||||
type EventHandler = (self: AgsMenuItem) => boolean | unknown;
|
||||
export interface MenuItemProps extends BaseProps<AgsMenuItem>, Gtk.MenuItem.ConstructorProperties {
|
||||
on_activate?: EventHandler
|
||||
on_select?: EventHandler
|
||||
on_deselct?: EventHandler
|
||||
}
|
||||
|
||||
export class AgsMenuItem extends Gtk.MenuItem {
|
||||
static { GObject.registerClass(this); }
|
||||
export class AgsMenuItem extends AgsWidget(Gtk.MenuItem) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsMenuItem',
|
||||
Properties: {
|
||||
'on-activate': Service.pspec('on-activate', 'jsobject', 'rw'),
|
||||
'on-select': Service.pspec('on-select', 'jsobject', 'rw'),
|
||||
'on-deselect': Service.pspec('on-deselect', 'jsobject', 'rw'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
onActivate: Command;
|
||||
onSelect: Command;
|
||||
onDeselect: Command;
|
||||
constructor(props: MenuItemProps = {}) {
|
||||
super(props);
|
||||
|
||||
constructor({
|
||||
onActivate = '',
|
||||
onSelect = '',
|
||||
onDeselect = '',
|
||||
...rest
|
||||
}: MenuItemProps = {}) {
|
||||
super(rest);
|
||||
this.connect('activate', () => this.on_activate?.(this));
|
||||
this.connect('select', () => this.on_select?.(this));
|
||||
this.connect('deselect', () => this.on_deselect?.(this));
|
||||
}
|
||||
|
||||
this.onActivate = onActivate;
|
||||
this.onSelect = onSelect;
|
||||
this.onDeselect = onDeselect;
|
||||
get on_activate() { return this._get('on-activate'); }
|
||||
set on_activate(callback: MenuItemProps['on_activate']) {
|
||||
this._set('on-activate', callback);
|
||||
}
|
||||
|
||||
this.connect('activate', (...args) => runCmd(this.onActivate, ...args));
|
||||
this.connect('select', (...args) => runCmd(this.onSelect, ...args));
|
||||
this.connect('deselect', (...args) => runCmd(this.onDeselect, ...args));
|
||||
get on_select() { return this._get('on-select'); }
|
||||
set on_select(callback: EventHandler) {
|
||||
this._set('on-select', callback);
|
||||
}
|
||||
|
||||
get on_deselect() { return this._get('on-deselect'); }
|
||||
set on_deselect(callback: EventHandler) {
|
||||
this._set('on-deselect', callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface OverlayProps extends Gtk.Overlay.ConstructorProperties {
|
||||
export interface OverlayProps extends BaseProps<AgsOverlay>, Gtk.Overlay.ConstructorProperties {
|
||||
pass_through?: boolean
|
||||
overlays?: Gtk.Widget[]
|
||||
}
|
||||
|
||||
export default class AgsOverlay extends Gtk.Overlay {
|
||||
export default class AgsOverlay extends AgsWidget(Gtk.Overlay) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsOverlay',
|
||||
Properties: {
|
||||
'pass-through': Service.pspec('pass-through', 'boolean', 'rw'),
|
||||
'overlays': Service.pspec('overlays', 'jsobject', 'rw'),
|
||||
@@ -17,6 +19,8 @@ export default class AgsOverlay extends Gtk.Overlay {
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(props: OverlayProps = {}) { super(props); }
|
||||
|
||||
get pass_through() {
|
||||
return this.get_children()
|
||||
.map(ch => this.get_overlay_pass_through(ch))
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface ProgressBarProps extends Gtk.ProgressBar.ConstructorProperties {
|
||||
export interface ProgressBarProps extends
|
||||
BaseProps<AgsProgressBar>, Gtk.ProgressBar.ConstructorProperties {
|
||||
vertical?: boolean
|
||||
value?: number
|
||||
}
|
||||
|
||||
export default class AgsProgressBar extends Gtk.ProgressBar {
|
||||
export default class AgsProgressBar extends AgsWidget(Gtk.ProgressBar) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsProgressBar',
|
||||
Properties: {
|
||||
'vertical': Service.pspec('vertical', 'boolean', 'rw'),
|
||||
'value': Service.pspec('value', 'float', 'rw'),
|
||||
@@ -17,6 +20,8 @@ export default class AgsProgressBar extends Gtk.ProgressBar {
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(props: ProgressBarProps = {}) { super(props); }
|
||||
|
||||
get value() { return this.fraction; }
|
||||
set value(value: number) {
|
||||
if (this.value === value)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
@@ -10,19 +11,22 @@ const transitions = [
|
||||
|
||||
type Transition = typeof transitions[number];
|
||||
|
||||
export interface RevealerProps extends Gtk.Revealer.ConstructorProperties {
|
||||
transitions?: Transition
|
||||
export interface RevealerProps extends BaseProps<AgsRevealer>, Gtk.Revealer.ConstructorProperties {
|
||||
transition?: Transition
|
||||
}
|
||||
|
||||
export default class AgsRevealer extends Gtk.Revealer {
|
||||
export default class AgsRevealer extends AgsWidget(Gtk.Revealer) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsRevealer',
|
||||
Properties: {
|
||||
'transition': Service.pspec('transition', 'string', 'rw'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(props: RevealerProps = {}) { super(props); }
|
||||
|
||||
get transition() { return transitions[this.transition_type]; }
|
||||
set transition(transition: Transition) {
|
||||
if (!transition || this.transition === transition)
|
||||
@@ -34,5 +38,6 @@ export default class AgsRevealer extends Gtk.Revealer {
|
||||
}
|
||||
|
||||
this.transition_type = transitions.findIndex(t => t === transition);
|
||||
this.notify('transition');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
@@ -5,14 +6,16 @@ import Service from '../service.js';
|
||||
const policy = ['automatic', 'always', 'never', 'external'] as const;
|
||||
type Policy = typeof policy[number]
|
||||
|
||||
export interface ScrollableProps extends Gtk.ScrolledWindow.ConstructorProperties {
|
||||
export interface ScrollableProps extends
|
||||
BaseProps<AgsScrollable>, Gtk.ScrolledWindow.ConstructorProperties {
|
||||
hscroll?: Policy,
|
||||
vscroll?: Policy,
|
||||
}
|
||||
|
||||
export default class AgsScrollable extends Gtk.ScrolledWindow {
|
||||
export default class AgsScrollable extends AgsWidget(Gtk.ScrolledWindow) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsScrollable',
|
||||
Properties: {
|
||||
'hscroll': Service.pspec('hscroll', 'string', 'rw'),
|
||||
'vscroll': Service.pspec('vscroll', 'string', 'rw'),
|
||||
@@ -20,16 +23,15 @@ export default class AgsScrollable extends Gtk.ScrolledWindow {
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(params: ScrollableProps = {}) {
|
||||
constructor(props: ScrollableProps = {}) {
|
||||
super({
|
||||
...params,
|
||||
...props,
|
||||
hadjustment: new Gtk.Adjustment(),
|
||||
vadjustment: new Gtk.Adjustment(),
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get hscroll() { return this._hscroll as Policy; }
|
||||
get hscroll() { return this._get('hscroll'); }
|
||||
set hscroll(hscroll: Policy) {
|
||||
if (!hscroll || this.hscroll === hscroll)
|
||||
return;
|
||||
@@ -39,14 +41,11 @@ export default class AgsScrollable extends Gtk.ScrolledWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
this._hscroll = hscroll;
|
||||
this.notify('hscroll');
|
||||
this.policy();
|
||||
this._set('hscroll', hscroll);
|
||||
this._policy();
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get vscroll() { return this._vscroll as Policy; }
|
||||
get vscroll() { return this._get('vscroll'); }
|
||||
set vscroll(vscroll: Policy) {
|
||||
if (!vscroll || this.vscroll === vscroll)
|
||||
return;
|
||||
@@ -56,13 +55,11 @@ export default class AgsScrollable extends Gtk.ScrolledWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
this._vscroll = vscroll;
|
||||
this.notify('vscroll');
|
||||
this.policy();
|
||||
this._set('vscroll', vscroll);
|
||||
this._policy();
|
||||
}
|
||||
|
||||
policy() {
|
||||
private _policy() {
|
||||
const hscroll = policy.findIndex(p => p === this.hscroll);
|
||||
const vscroll = policy.findIndex(p => p === this.vscroll);
|
||||
this.set_policy(
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Gdk from 'gi://Gdk?version=3.0';
|
||||
import { runCmd } from '../utils.js';
|
||||
import { type Command } from './widget.js';
|
||||
import Service from '../service.js';
|
||||
|
||||
export interface SliderProps extends Gtk.Scale.ConstructorProperties {
|
||||
onChange?: Command
|
||||
type EventHandler = (self: AgsSlider, event: Gdk.Event) => void | unknown;
|
||||
|
||||
export interface SliderProps extends BaseProps<AgsSlider>, Gtk.Scale.ConstructorProperties {
|
||||
on_change?: EventHandler,
|
||||
value?: number
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
}
|
||||
|
||||
export default class AgsSlider extends Gtk.Scale {
|
||||
export default class AgsSlider extends AgsWidget(Gtk.Scale) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsSlider',
|
||||
Properties: {
|
||||
'dragging': Service.pspec('dragging', 'boolean', 'r'),
|
||||
'vertical': Service.pspec('vertical', 'boolean', 'rw'),
|
||||
@@ -23,14 +25,12 @@ export default class AgsSlider extends Gtk.Scale {
|
||||
'min': Service.pspec('min', 'double', 'rw'),
|
||||
'max': Service.pspec('max', 'double', 'rw'),
|
||||
'step': Service.pspec('step', 'double', 'rw'),
|
||||
'on-change': Service.pspec('on-change', 'jsobject', 'rw'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
onChange: Command;
|
||||
|
||||
constructor({
|
||||
onChange = '',
|
||||
value = 0,
|
||||
min = 0,
|
||||
max = 1,
|
||||
@@ -43,24 +43,21 @@ export default class AgsSlider extends Gtk.Scale {
|
||||
lower: min,
|
||||
upper: max,
|
||||
step_increment: step,
|
||||
value: value,
|
||||
}),
|
||||
});
|
||||
|
||||
this.onChange = onChange;
|
||||
|
||||
this.adjustment.connect('notify::value', ({ value }, event) => {
|
||||
this.adjustment.connect('notify::value', (_, event: Gdk.Event) => {
|
||||
if (!this.dragging)
|
||||
return;
|
||||
|
||||
typeof this.onChange === 'function'
|
||||
? this.onChange(this, event, value)
|
||||
: runCmd((onChange as string).replace(/\{\}/g, `${value}`));
|
||||
this.on_change?.(this, event);
|
||||
});
|
||||
|
||||
if (value)
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
get on_change() { return this._get('on-change'); }
|
||||
set on_change(callback: EventHandler) { this._set('on-change', callback); }
|
||||
|
||||
get value() { return this.adjustment.value; }
|
||||
set value(value: number) {
|
||||
if (this.dragging || this.value === value)
|
||||
@@ -97,16 +94,8 @@ export default class AgsSlider extends Gtk.Scale {
|
||||
this.notify('step');
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get dragging() { return this._dragging; }
|
||||
set dragging(dragging: boolean) {
|
||||
if (this.dragging === dragging)
|
||||
return;
|
||||
|
||||
// @ts-expect-error
|
||||
this._dragging = dragging;
|
||||
this.notify('dragging');
|
||||
}
|
||||
get dragging() { return this._get('dragging'); }
|
||||
set dragging(dragging: boolean) { this._set('dragging', dragging); }
|
||||
|
||||
get vertical() { return this.orientation === Gtk.Orientation.VERTICAL; }
|
||||
set vertical(vertical) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Service from '../service.js';
|
||||
@@ -13,15 +14,16 @@ const transitions = [
|
||||
|
||||
type Transition = typeof transitions[number]
|
||||
|
||||
export interface StackProps extends Gtk.Stack.ConstructorProperties {
|
||||
export interface StackProps extends BaseProps<AgsStack>, Gtk.Stack.ConstructorProperties {
|
||||
shown?: string
|
||||
items?: [string, Gtk.Widget][]
|
||||
transition?: Transition
|
||||
}
|
||||
|
||||
export default class AgsStack extends Gtk.Stack {
|
||||
export default class AgsStack extends AgsWidget(Gtk.Stack) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsStack',
|
||||
Properties: {
|
||||
'transition': Service.pspec('transition', 'string', 'rw'),
|
||||
'shown': Service.pspec('shown', 'string', 'rw'),
|
||||
@@ -30,19 +32,19 @@ export default class AgsStack extends Gtk.Stack {
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(props: StackProps = {}) { super(props); }
|
||||
|
||||
add_named(child: Gtk.Widget, name: string): void {
|
||||
this.items.push([name, child]);
|
||||
super.add_named(child, name);
|
||||
this.notify('items');
|
||||
}
|
||||
|
||||
get items() {
|
||||
// @ts-expect-error
|
||||
if (!Array.isArray(this._items))
|
||||
// @ts-expect-error
|
||||
this._items = [];
|
||||
if (!Array.isArray(this._get('items')))
|
||||
this._set('items', []);
|
||||
|
||||
// @ts-expect-error
|
||||
return this._items;
|
||||
return this._get('items');
|
||||
}
|
||||
|
||||
set items(items: [string, Gtk.Widget][]) {
|
||||
@@ -57,13 +59,11 @@ export default class AgsStack extends Gtk.Stack {
|
||||
.filter(([, ch]) => this.get_children().includes(ch))
|
||||
.forEach(([, ch]) => this.remove(ch));
|
||||
|
||||
// @ts-expect-error
|
||||
this._items = [];
|
||||
items.forEach(([name, widget]) => {
|
||||
widget && this.add_named(widget, name);
|
||||
widget && super.add_named(widget, name);
|
||||
});
|
||||
|
||||
this.notify('items');
|
||||
this._set('items', items);
|
||||
this.show_all();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,83 +1,90 @@
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import GLib from 'gi://GLib?version=2.0';
|
||||
import Service from '../service.js';
|
||||
import { interval, connect } from '../utils.js';
|
||||
import { interval } from '../utils.js';
|
||||
import { Variable } from '../variable.js';
|
||||
import { App } from '../app.js';
|
||||
|
||||
// TODO: remove this type and make them only functions
|
||||
export type Command = string | ((...args: unknown[]) => boolean);
|
||||
type KebabCase<S extends string> = S extends `${infer Prefix}_${infer Suffix}`
|
||||
? `${Prefix}-${KebabCase<Suffix>}` : S;
|
||||
|
||||
type OnlyString<S extends string | unknown> = S extends string ? S : never;
|
||||
|
||||
const aligns = ['fill', 'start', 'end', 'center', 'baseline'] as const;
|
||||
type Align = typeof aligns[number];
|
||||
|
||||
export interface BaseProps<Self> {
|
||||
className?: string
|
||||
classNames?: string[]
|
||||
style?: string
|
||||
type Property = [prop: string, value: unknown];
|
||||
|
||||
type Connection<Self> =
|
||||
| [GObject.Object, (self: Self, ...args: unknown[]) => unknown, string?]
|
||||
| [string, (self: Self, ...args: unknown[]) => unknown]
|
||||
| [number, (self: Self, ...args: unknown[]) => unknown];
|
||||
|
||||
type Bind = [
|
||||
prop: string,
|
||||
obj: GObject.Object,
|
||||
objProp?: string,
|
||||
transform?: (value: unknown) => unknown,
|
||||
];
|
||||
|
||||
export interface BaseProps<Self> extends Gtk.Widget.ConstructorProperties {
|
||||
class_name?: string
|
||||
class_names?: string[]
|
||||
css?: string
|
||||
halign?: Align
|
||||
valign?: Align
|
||||
connections?: (
|
||||
[string, (self: Self, ...args: unknown[]) => unknown] |
|
||||
[number, (self: Self, ...args: unknown[]) => unknown] |
|
||||
[GObject.Object, (self: Self, ...args: unknown[]) => unknown, string]
|
||||
)[]
|
||||
properties?: [prop: string, value: unknown][]
|
||||
binds?: [
|
||||
prop: string,
|
||||
obj: GObject.Object,
|
||||
objProp?: string,
|
||||
transform?: (value: unknown) => unknown,
|
||||
][],
|
||||
hpack?: Align
|
||||
vpack?: Align
|
||||
connections?: Connection<Self>[]
|
||||
properties?: Property[]
|
||||
binds?: Bind[],
|
||||
setup?: (self: Self) => void
|
||||
}
|
||||
|
||||
export default function <T extends typeof Gtk.Widget>(Widget: T) {
|
||||
// @ts-expect-error mixin constructor
|
||||
class AgsWidget extends Widget {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type WidgetCtor = new (...args: any[]) => Gtk.Widget;
|
||||
export default function <T extends WidgetCtor>(Widget: T, GTypeName?: string) {
|
||||
return class AgsWidget extends Widget {
|
||||
static {
|
||||
const pspec = (name: string) => GObject.ParamSpec.jsobject(
|
||||
name, name, name, GObject.ParamFlags.CONSTRUCT_ONLY | GObject.ParamFlags.WRITABLE);
|
||||
|
||||
GObject.registerClass({
|
||||
GTypeName: Widget.name,
|
||||
GTypeName: `Ags_${GTypeName || Widget.name}`,
|
||||
Properties: {
|
||||
'class-name': Service.pspec('class-name', 'string', 'rw'),
|
||||
'class-names': Service.pspec('class-names', 'jsobject', 'rw'),
|
||||
'css': Service.pspec('css', 'string', 'rw'),
|
||||
'hpack': Service.pspec('hpack', 'string', 'rw'),
|
||||
'vpack': Service.pspec('vpack', 'string', 'rw'),
|
||||
// order of these matter
|
||||
'properties': pspec('properties'),
|
||||
'setup': pspec('setup'),
|
||||
'connections': pspec('connections'),
|
||||
'binds': pspec('binds'),
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
constructor(params: BaseProps<InstanceType<AgsWidget & T>> & ConstructorParameters<T>[0]) {
|
||||
const {
|
||||
connections = [],
|
||||
properties = [],
|
||||
binds = [],
|
||||
style,
|
||||
halign,
|
||||
valign,
|
||||
setup,
|
||||
...rest
|
||||
} = params;
|
||||
super(typeof params === 'string' ? params : rest as Gtk.Widget.ConstructorProperties);
|
||||
_destroyed = false;
|
||||
|
||||
this.style = style!;
|
||||
this.halign = halign!;
|
||||
this.valign = valign!;
|
||||
// defining private fields for typescript causes
|
||||
// gobject constructor field setters to be overridden
|
||||
// so we use this _get and _set to avoid @ts-expect-error everywhere
|
||||
_get<T>(field: string) {
|
||||
return (this as unknown as { [key: string]: unknown })[`_${field}`] as T;
|
||||
}
|
||||
|
||||
const widget = this as InstanceType<AgsWidget & T>;
|
||||
_set<T>(field: string, value: T) {
|
||||
if (this._get(field) === value)
|
||||
return;
|
||||
|
||||
properties.forEach(([key, value]) => {
|
||||
(this as unknown as { [key: string]: unknown })[`_${key}`] = value;
|
||||
});
|
||||
(this as unknown as { [key: string]: T })[`_${field}`] = value;
|
||||
this.notify(field);
|
||||
}
|
||||
|
||||
binds.forEach(([prop, obj, objProp = 'value', transform = out => out]) => {
|
||||
if (!prop || !obj) {
|
||||
console.error(Error('missing arguments to binds'));
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const callback = () => this[prop] = transform(obj[objProp]);
|
||||
connections.push([obj, callback, `notify::${objProp}`]);
|
||||
});
|
||||
set connections(connections: Connection<AgsWidget>[]) {
|
||||
if (!connections)
|
||||
return;
|
||||
|
||||
connections.forEach(([s, callback, event]) => {
|
||||
if (!s || !callback) {
|
||||
@@ -89,49 +96,116 @@ export default function <T extends typeof Gtk.Widget>(Widget: T) {
|
||||
this.connect(s, callback);
|
||||
|
||||
else if (typeof s === 'number')
|
||||
interval(s, () => callback(widget), widget);
|
||||
interval(s, () => callback(this), this);
|
||||
|
||||
else if (s instanceof GObject.Object)
|
||||
connect(s, widget, callback as (w: Gtk.Widget) => void, event);
|
||||
this.connectTo(s, callback, event);
|
||||
|
||||
else
|
||||
console.error(Error(`${s} is not a GObject nor a string nor a number`));
|
||||
});
|
||||
|
||||
if (typeof setup === 'function')
|
||||
setup(widget);
|
||||
}
|
||||
|
||||
// @ts-expect-error prop override
|
||||
get halign() { return aligns[super.halign]; }
|
||||
set binds(binds: Bind[]) {
|
||||
if (!binds)
|
||||
return;
|
||||
|
||||
// @ts-expect-error prop override
|
||||
set halign(align: Align) {
|
||||
binds.forEach(([prop, obj, objProp = 'value', transform = out => out]) => {
|
||||
this.bind(
|
||||
prop as KebabCase<OnlyString<keyof this>>,
|
||||
obj,
|
||||
objProp as keyof typeof obj,
|
||||
transform,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
set properties(properties: Property[]) {
|
||||
if (!properties)
|
||||
return;
|
||||
|
||||
properties.forEach(([key, value]) => {
|
||||
(this as unknown as { [key: string]: unknown })[`_${key}`] = value;
|
||||
});
|
||||
}
|
||||
|
||||
set setup(setup: (self: AgsWidget) => void) {
|
||||
if (!setup)
|
||||
return;
|
||||
|
||||
setup(this);
|
||||
}
|
||||
|
||||
connectTo<GObject extends GObject.Object>(
|
||||
o: GObject | Service,
|
||||
callback: (self: typeof this, ...args: unknown[]) => void,
|
||||
event?: string,
|
||||
) {
|
||||
if (!(o instanceof GObject.Object)) {
|
||||
console.error(Error(`${o} is not a GObject`));
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!(o instanceof Service || o instanceof App || o instanceof Variable) && !event) {
|
||||
console.error(Error('you are trying to connect to a regular GObject ' +
|
||||
'without specifying the signal'));
|
||||
return this;
|
||||
}
|
||||
|
||||
const id = o.connect(event!, (_, ...args: unknown[]) => callback(this, ...args));
|
||||
|
||||
this.connect('destroy', () => {
|
||||
this._destroyed = true;
|
||||
o.disconnect(id);
|
||||
});
|
||||
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||
if (!this._destroyed)
|
||||
callback(this);
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
bind<GObject extends GObject.Object>(
|
||||
prop: KebabCase<OnlyString<keyof typeof this>>,
|
||||
target: GObject,
|
||||
targetProp: OnlyString<keyof GObject>,
|
||||
// FIXME: typeof target[targetProp]
|
||||
transform: (value: typeof target[typeof targetProp]) => unknown = out => out,
|
||||
) {
|
||||
// @ts-expect-error readonly property
|
||||
const callback = () => this[prop] = transform(target[targetProp]);
|
||||
this.connectTo(target, callback, `notify::${targetProp}`);
|
||||
return this;
|
||||
}
|
||||
|
||||
get hpack() { return aligns[this.halign]; }
|
||||
set hpack(align: Align) {
|
||||
if (!align)
|
||||
return;
|
||||
|
||||
if (!aligns.includes(align)) {
|
||||
console.error(new Error(`halign has to be on of ${aligns}`));
|
||||
console.error(Error(`halign has to be on of ${aligns}`));
|
||||
return;
|
||||
}
|
||||
|
||||
super.halign = aligns.findIndex(a => a === align);
|
||||
this.halign = aligns.findIndex(a => a === align);
|
||||
}
|
||||
|
||||
// @ts-expect-error prop override
|
||||
get valign() { return aligns[super.valign]; }
|
||||
|
||||
// @ts-expect-error prop override
|
||||
set valign(align: Align) {
|
||||
get vpack() { return aligns[this.valign]; }
|
||||
set vpack(align: Align) {
|
||||
if (!align)
|
||||
return;
|
||||
|
||||
if (!aligns.includes(align)) {
|
||||
console.error(new Error(`valign has to be on of ${aligns}`));
|
||||
console.error(Error(`valign has to be on of ${aligns}`));
|
||||
return;
|
||||
}
|
||||
|
||||
super.valign = aligns.findIndex(a => a === align);
|
||||
this.valign = aligns.findIndex(a => a === align);
|
||||
}
|
||||
|
||||
toggleClassName(className: string, condition = true) {
|
||||
@@ -161,8 +235,11 @@ export default function <T extends typeof Gtk.Widget>(Widget: T) {
|
||||
names.forEach(cn => this.toggleClassName(cn));
|
||||
}
|
||||
|
||||
private _cssProvider!: Gtk.CssProvider;
|
||||
_cssProvider!: Gtk.CssProvider;
|
||||
setCss(css: string) {
|
||||
if (!css.includes('{') || !css.includes('}'))
|
||||
css = `* { ${css} }`;
|
||||
|
||||
if (this._cssProvider)
|
||||
this.get_style_context().remove_provider(this._cssProvider);
|
||||
|
||||
@@ -174,35 +251,15 @@ export default function <T extends typeof Gtk.Widget>(Widget: T) {
|
||||
this.notify('css');
|
||||
}
|
||||
|
||||
setStyle(css: string) {
|
||||
this.setCss(`* { ${css} }`);
|
||||
this.notify('style');
|
||||
get css() {
|
||||
return this._cssProvider.to_string() || '';
|
||||
}
|
||||
|
||||
// @ts-expect-error prop override
|
||||
get style() { return this._style || ''; }
|
||||
|
||||
// @ts-expect-error prop override
|
||||
set style(css: string) {
|
||||
if (!css)
|
||||
return;
|
||||
|
||||
// @ts-expect-error
|
||||
this._style = css;
|
||||
this.setCss(`* { ${css} }`);
|
||||
this.notify('style');
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get css() { return this._css || ''; }
|
||||
set css(css: string) {
|
||||
if (!css)
|
||||
return;
|
||||
|
||||
// @ts-expect-error
|
||||
this._css = css;
|
||||
this.setCss(css);
|
||||
this.notify('css');
|
||||
}
|
||||
|
||||
get child(): Gtk.Widget | null {
|
||||
@@ -220,20 +277,7 @@ export default function <T extends typeof Gtk.Widget>(Widget: T) {
|
||||
// @ts-expect-error
|
||||
this.set_child(child);
|
||||
else
|
||||
console.error(new Error(`can't set child on ${this}`));
|
||||
console.error(Error(`can't set child on ${this}`));
|
||||
}
|
||||
|
||||
// @ts-expect-error prop override
|
||||
get parent(): Gtk.Container | null {
|
||||
return this.get_parent() as Gtk.Container || null;
|
||||
}
|
||||
}
|
||||
|
||||
return (params:
|
||||
BaseProps<InstanceType<AgsWidget & T>> &
|
||||
ConstructorParameters<T>[0] |
|
||||
string,
|
||||
) => {
|
||||
return new AgsWidget(params) as InstanceType<AgsWidget & T>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AgsWidget, { type BaseProps } from './widget.js';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import Gdk from 'gi://Gdk?version=3.0';
|
||||
@@ -11,26 +12,27 @@ const anchors = ['left', 'right', 'top', 'bottom'] as const;
|
||||
type Layer = typeof layers[number]
|
||||
type Anchor = typeof anchors[number]
|
||||
|
||||
export interface WindowProps extends Omit<Gtk.Window.ConstructorProperties, 'margin'> {
|
||||
export interface WindowProps extends BaseProps<AgsWindow>, Gtk.Window.ConstructorProperties {
|
||||
anchor?: Anchor[]
|
||||
exclusive?: boolean
|
||||
focusable?: boolean
|
||||
layer?: Layer
|
||||
margin?: number[]
|
||||
margins?: number[]
|
||||
monitor?: number
|
||||
popup?: boolean
|
||||
visible?: boolean
|
||||
}
|
||||
|
||||
export default class AgsWindow extends Gtk.Window {
|
||||
export default class AgsWindow extends AgsWidget(Gtk.Window) {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
GTypeName: 'AgsWindow',
|
||||
Properties: {
|
||||
'anchor': Service.pspec('anchor', 'jsobject', 'rw'),
|
||||
'exclusive': Service.pspec('exclusive', 'boolean', 'rw'),
|
||||
'focusable': Service.pspec('focusable', 'boolean', 'rw'),
|
||||
'layer': Service.pspec('layer', 'string', 'rw'),
|
||||
'margin': Service.pspec('margin', 'jsobject', 'rw'),
|
||||
'margins': Service.pspec('margins', 'jsobject', 'rw'),
|
||||
'monitor': Service.pspec('monitor', 'int', 'rw'),
|
||||
'popup': Service.pspec('popup', 'boolean', 'rw'),
|
||||
},
|
||||
@@ -44,7 +46,7 @@ export default class AgsWindow extends Gtk.Window {
|
||||
exclusive = false,
|
||||
focusable = false,
|
||||
layer = 'top',
|
||||
margin = [],
|
||||
margins = [],
|
||||
monitor = -1,
|
||||
popup = false,
|
||||
visible = true,
|
||||
@@ -58,25 +60,22 @@ export default class AgsWindow extends Gtk.Window {
|
||||
this.exclusive = exclusive;
|
||||
this.focusable = focusable;
|
||||
this.layer = layer;
|
||||
this.margin = margin;
|
||||
this.margins = margins;
|
||||
this.monitor = monitor;
|
||||
this.show_all();
|
||||
this.popup = popup;
|
||||
this.visible = visible === true || visible === null && !popup;
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get monitor() { return this._monitor; }
|
||||
get monitor(): Gdk.Monitor { return this._get('monitor'); }
|
||||
set monitor(monitor: number) {
|
||||
if (monitor < 0 || this.monitor === monitor)
|
||||
if (monitor < 0)
|
||||
return;
|
||||
|
||||
const m = Gdk.Display.get_default()?.get_monitor(monitor);
|
||||
if (m) {
|
||||
LayerShell.set_monitor(this, m);
|
||||
// @ts-expect-error
|
||||
this._monitor = monitor;
|
||||
this.notify('monitor');
|
||||
this._set('monitor', monitor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,15 +129,13 @@ export default class AgsWindow extends Gtk.Window {
|
||||
this.notify('anchor');
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get margin() {
|
||||
get margins() {
|
||||
return ['TOP', 'RIGHT', 'BOTTOM', 'LEFT'].map(edge =>
|
||||
LayerShell.get_margin(this, LayerShell.Edge[edge]),
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
set margin(margin: number[]) {
|
||||
set margins(margin: number[]) {
|
||||
let margins: [side: string, index: number][] = [];
|
||||
switch (margin.length) {
|
||||
case 1:
|
||||
@@ -162,33 +159,26 @@ export default class AgsWindow extends Gtk.Window {
|
||||
LayerShell.Edge[side], (margin as number[])[i]),
|
||||
);
|
||||
|
||||
this.notify('margin');
|
||||
this.notify('margins');
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
get popup() { return !!this._popup; }
|
||||
|
||||
// this will be removed in gtk4
|
||||
get popup() { return !!this._get('popup'); }
|
||||
set popup(popup: boolean) {
|
||||
if (this.popup === popup)
|
||||
return;
|
||||
|
||||
// @ts-expect-error
|
||||
if (this._popup)
|
||||
// @ts-expect-error
|
||||
this.disconnect(this._popup);
|
||||
if (this.popup)
|
||||
this.disconnect(this._get('popup'));
|
||||
|
||||
if (popup) {
|
||||
this.connect('key-press-event', (_, event) => {
|
||||
this._set('popup', this.connect('key-press-event', (_, event: Gdk.Event) => {
|
||||
if (event.get_keyval()[1] === Gdk.KEY_Escape) {
|
||||
App.getWindow(this.name!)
|
||||
? App.closeWindow(this.name!)
|
||||
: this.hide();
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
this.notify('popup');
|
||||
}
|
||||
|
||||
get focusable() {
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2017"],
|
||||
"lib": [
|
||||
"ES2022"
|
||||
],
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"outDir": "_build/tsc-out",
|
||||
|
||||
2
types/ambient.d.ts
vendored
2
types/ambient.d.ts
vendored
@@ -16,6 +16,8 @@ declare module console {
|
||||
export function error(msg: string, subsitutions?: any[]): void;
|
||||
export function log(obj: object, others?: object[]): void;
|
||||
export function log(msg: string, subsitutions?: any[]): void;
|
||||
export function warn(obj: object, others?: object[]): void;
|
||||
export function warn(msg: string, subsitutions?: any[]): void;
|
||||
}
|
||||
|
||||
declare interface String {
|
||||
|
||||
Reference in New Issue
Block a user