forked from Shinonome/dots-hyprland
ags: sync
This commit is contained in:
@@ -74,18 +74,16 @@ export const ModuleLeftSpace = () => Widget.EventBox({
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
className: 'txt-smaller bar-topdesc txt',
|
||||
connections: [[Hyprland.active.client, label => { // Hyprland.active.client
|
||||
setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
|
||||
label.label = Hyprland.active.client._class.length === 0 ? 'Desktop' : Hyprland.active.client._class;
|
||||
}]],
|
||||
}),
|
||||
}),
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
className: 'txt txt-smallie',
|
||||
connections: [
|
||||
[Hyprland.active.client, label => { // Hyprland.active.client
|
||||
label.label = Hyprland.active.client._title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : truncateTitle(Hyprland.active.client._title);
|
||||
}]
|
||||
],
|
||||
setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
|
||||
label.label = Hyprland.active.client._title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : truncateTitle(Hyprland.active.client._title);
|
||||
}),
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
@@ -6,19 +6,19 @@ import { ModuleLeftSpace } from "./leftspace.js";
|
||||
import { ModuleMusic } from "./music.js";
|
||||
import { ModuleRightSpace } from "./rightspace.js";
|
||||
import { ModuleSystem } from "./system.js";
|
||||
import { ModuleWorkspaces } from "./workspaces.js";
|
||||
import ModuleWorkspaces from "./workspaces.js";
|
||||
import { RoundedCorner } from "../../lib/roundedcorner.js";
|
||||
|
||||
const left = Widget.Box({
|
||||
className: 'bar-sidemodule',
|
||||
children: [ModuleMusic()],
|
||||
children: [
|
||||
ModuleMusic()
|
||||
],
|
||||
});
|
||||
|
||||
const center = Widget.Box({
|
||||
children: [
|
||||
RoundedCorner('topright', { className: 'corner-bar-group' }),
|
||||
ModuleWorkspaces(),
|
||||
RoundedCorner('topleft', { className: 'corner-bar-group' }),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ export default () => Widget.Window({
|
||||
className: 'bar-bg',
|
||||
startWidget: ModuleLeftSpace(),
|
||||
centerWidget: Widget.Box({
|
||||
className: 'spacing-h--20',
|
||||
className: 'spacing-h-4',
|
||||
children: [
|
||||
left,
|
||||
center,
|
||||
|
||||
@@ -9,12 +9,12 @@ const TrackProgress = () => {
|
||||
const _updateProgress = (circprog) => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
if (!mpris) return;
|
||||
// Set circular progress (font size cuz that's how this hacky circprog works)
|
||||
// Set circular progress value
|
||||
circprog.css = `font-size: ${Math.max(mpris.position / mpris.length * 100, 0)}px;`
|
||||
}
|
||||
return AnimatedCircProg({
|
||||
className: 'bar-music-circprog',
|
||||
vpack: 'center',
|
||||
vpack: 'center', hpack: 'center',
|
||||
connections: [ // Update on change/once every 3 seconds
|
||||
[Mpris, _updateProgress],
|
||||
[3000, _updateProgress]
|
||||
@@ -65,7 +65,7 @@ export const ModuleMusic = () => Widget.EventBox({
|
||||
Widget.Scrollable({
|
||||
hexpand: true,
|
||||
child: Widget.Label({
|
||||
className: 'txt txt-smallie',
|
||||
className: 'txt-smallie txt-onSurfaceVariant',
|
||||
connections: [[Mpris, label => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
if (mpris)
|
||||
|
||||
@@ -11,11 +11,11 @@ export const ModuleRightSpace = () => {
|
||||
const barTray = Tray();
|
||||
const barStatusIcons = StatusIcons({
|
||||
className: 'bar-statusicons',
|
||||
connections: [[App, (self, currentName, visible) => {
|
||||
setup: (self) => self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideright') {
|
||||
self.toggleClassName('bar-statusicons-active', visible);
|
||||
}
|
||||
}]],
|
||||
}),
|
||||
});
|
||||
|
||||
return Widget.EventBox({
|
||||
|
||||
+130
-103
@@ -1,146 +1,173 @@
|
||||
// This is for the right pill of the bar.
|
||||
// For the cool memory indicator on the sidebar, see sysinfo.js
|
||||
import { Service, Utils, Widget } from '../../imports.js';
|
||||
const { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = Widget;
|
||||
const { exec, execAsync } = Utils;
|
||||
const { GLib } = imports.gi;
|
||||
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
|
||||
import { MaterialIcon } from '../../lib/materialicon.js';
|
||||
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
|
||||
|
||||
const BATTERY_LOW = 20;
|
||||
|
||||
const BatBatteryProgress = () => {
|
||||
const _updateProgress = (circprog) => { // Set circular progress value
|
||||
circprog.css = `font-size: ${Battery.percent}px;`
|
||||
|
||||
circprog.toggleClassName('bar-batt-circprog-low', Battery.percent <= BATTERY_LOW);
|
||||
circprog.toggleClassName('bar-batt-circprog-full', Battery.charged);
|
||||
}
|
||||
return AnimatedCircProg({
|
||||
className: 'bar-batt-circprog',
|
||||
vpack: 'center', hpack: 'center',
|
||||
connections: [
|
||||
[Battery, _updateProgress],
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const BarClock = () => Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'spacing-h-5',
|
||||
className: 'spacing-h-5 txt-onSurfaceVariant',
|
||||
children: [
|
||||
Widget.Label({
|
||||
className: 'bar-clock',
|
||||
connections: [[5000, label => {
|
||||
label: GLib.DateTime.new_now_local().format("%H:%M"),
|
||||
setup: (self) => self.poll(5000, label => {
|
||||
label.label = GLib.DateTime.new_now_local().format("%H:%M");
|
||||
}]],
|
||||
}),
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'txt-norm txt',
|
||||
className: 'txt-norm',
|
||||
label: '•',
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'txt-smallie txt',
|
||||
connections: [[5000, label => {
|
||||
className: 'txt-smallie',
|
||||
label: GLib.DateTime.new_now_local().format("%A, %d/%m"),
|
||||
setup: (self) => self.poll(5000, label => {
|
||||
label.label = GLib.DateTime.new_now_local().format("%A, %d/%m");
|
||||
}]],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const BarBattery = () => {
|
||||
const BarResourceValue = (name, icon, command) => Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'bar-batt spacing-h-5',
|
||||
children: [
|
||||
MaterialIcon(icon, 'small'),
|
||||
Widget.ProgressBar({ // Progress
|
||||
vpack: 'center', hexpand: true,
|
||||
className: 'bar-prog-batt',
|
||||
connections: [[5000, (progress) => execAsync(['bash', '-c', command])
|
||||
.then((output) => {
|
||||
progress.value = Number(output) / 100;
|
||||
progress.tooltipText = `${name}: ${Number(output)}%`
|
||||
})
|
||||
.catch(print)
|
||||
]],
|
||||
}),
|
||||
]
|
||||
});
|
||||
const batteryWidget = Widget.Box({
|
||||
vpack: 'center',
|
||||
hexpand: true,
|
||||
className: 'spacing-h-5 bar-batt',
|
||||
connections: [[Battery, box => {
|
||||
box.toggleClassName('bar-batt-low', Battery.percent <= BATTERY_LOW);
|
||||
box.toggleClassName('bar-batt-full', Battery.charged);
|
||||
}]],
|
||||
children: [
|
||||
MaterialIcon('settings_heart', 'small'),
|
||||
Widget.Label({ // Percentage
|
||||
className: 'bar-batt-percentage',
|
||||
connections: [[Battery, label => {
|
||||
label.label = `${Battery.percent}`;
|
||||
}]],
|
||||
}),
|
||||
Widget.ProgressBar({ // Progress
|
||||
vpack: 'center',
|
||||
hexpand: true,
|
||||
className: 'bar-prog-batt',
|
||||
connections: [[Battery, progress => {
|
||||
progress.value = Math.abs(Battery.percent / 100); // battery could be initially negative wtf
|
||||
progress.toggleClassName('bar-prog-batt-low', Battery.percent <= BATTERY_LOW);
|
||||
progress.toggleClassName('bar-prog-batt-full', Battery.charged);
|
||||
batteryWidget.tooltipText = `Battery: ${Battery.percent}%`
|
||||
}]],
|
||||
}),
|
||||
Widget.Revealer({ // A dot for charging state
|
||||
transitionDuration: 150,
|
||||
revealChild: false,
|
||||
transition: 'slide_left',
|
||||
child: Widget.Box({
|
||||
className: 'spacing-h-3',
|
||||
children: [
|
||||
Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'bar-batt-chargestate-charging-smaller',
|
||||
connections: [[Battery, box => {
|
||||
box.toggleClassName('bar-batt-chargestate-low', Battery.percent <= BATTERY_LOW);
|
||||
box.toggleClassName('bar-batt-chargestate-full', Battery.charged);
|
||||
}]],
|
||||
}),
|
||||
Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'bar-batt-chargestate-charging',
|
||||
connections: [[Battery, box => {
|
||||
box.toggleClassName('bar-batt-chargestate-low', Battery.percent <= BATTERY_LOW);
|
||||
box.toggleClassName('bar-batt-chargestate-full', Battery.charged);
|
||||
}]],
|
||||
}),
|
||||
]
|
||||
}),
|
||||
connections: [[Battery, revealer => {
|
||||
revealer.revealChild = Battery.charging;
|
||||
}]],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const memUsage = Widget.Box({
|
||||
const UtilButton = ({ name, icon, onClicked }) => Button({
|
||||
vpack: 'center',
|
||||
tooltipText: name,
|
||||
onClicked: onClicked,
|
||||
className: 'bar-util-btn icon-material txt-norm',
|
||||
label: `${icon}`,
|
||||
})
|
||||
|
||||
const Utilities = () => Scrollable({
|
||||
hexpand: true,
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
BarResourceValue('RAM usage', 'memory', `free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`),
|
||||
BarResourceValue('Swap usage', 'swap_horiz', `free | awk '/^Swap/ {printf("%.2f\\n", ($3/$2) * 100)}'`),
|
||||
UtilButton({
|
||||
name: 'Screen snip', icon: 'screenshot_region', onClicked: () => {
|
||||
Utils.execAsync(['bash', '-c', `grim -g "$(slurp -d -c e2e2e2BB -b 31313122 -s 00000000)" - | wl-copy &`])
|
||||
.catch(print)
|
||||
}
|
||||
}),
|
||||
UtilButton({
|
||||
name: 'Color picker', icon: 'colorize', onClicked: () => {
|
||||
Utils.execAsync(['hyprpicker', '-a']).catch(print)
|
||||
}
|
||||
}),
|
||||
UtilButton({
|
||||
name: 'Toggle on-screen keyboard', icon: 'keyboard', onClicked: () => {
|
||||
App.toggleWindow('osk');
|
||||
}
|
||||
}),
|
||||
]
|
||||
})
|
||||
const widgetStack = Widget.Stack({
|
||||
transition: 'slide_up_down',
|
||||
vpack: 'center',
|
||||
hexpand: true,
|
||||
items: [
|
||||
['fallback', memUsage],
|
||||
['battery', batteryWidget],
|
||||
],
|
||||
setup: (stack) => Utils.timeout(1, () => {
|
||||
if (Battery.available) stack.shown = 'battery';
|
||||
else stack.shown = 'fallback';
|
||||
})
|
||||
})
|
||||
return widgetStack;
|
||||
}
|
||||
})
|
||||
|
||||
const BarBattery = () => Box({
|
||||
className: 'spacing-h-4 txt-onSurfaceVariant',
|
||||
children: [
|
||||
// Revealer({ // A dot for charging state
|
||||
// transitionDuration: 150,
|
||||
// revealChild: false,
|
||||
// transition: 'crossfade',
|
||||
// child: Widget.Box({
|
||||
// className: 'spacing-h-3',
|
||||
// children: [
|
||||
// Widget.Box({
|
||||
// vpack: 'center',
|
||||
// className: 'bar-batt-chargestate-charging-smaller',
|
||||
// setup: (self) => self.hook(Battery, box => {
|
||||
// box.toggleClassName('bar-batt-chargestate-low', Battery.percent <= BATTERY_LOW);
|
||||
// box.toggleClassName('bar-batt-chargestate-full', Battery.charged);
|
||||
// }),
|
||||
// }),
|
||||
// Widget.Box({
|
||||
// vpack: 'center',
|
||||
// className: 'bar-batt-chargestate-charging',
|
||||
// setup: (self) => self.hook(Battery, box => {
|
||||
// box.toggleClassName('bar-batt-chargestate-low', Battery.percent <= BATTERY_LOW);
|
||||
// box.toggleClassName('bar-batt-chargestate-full', Battery.charged);
|
||||
// }),
|
||||
// }),
|
||||
// ]
|
||||
// }),
|
||||
// setup: (self) => self.hook(Battery, revealer => {
|
||||
// revealer.revealChild = Battery.charging;
|
||||
// }),
|
||||
// }),
|
||||
Stack({
|
||||
transition: 'slide_up_down',
|
||||
items: [
|
||||
['discharging', Widget.Label({
|
||||
className: 'txt-norm txt',
|
||||
label: '•',
|
||||
}),],
|
||||
['charging', MaterialIcon('bolt', 'norm')],
|
||||
],
|
||||
setup: (self) => self.hook(Battery, revealer => {
|
||||
self.shown = Battery.charging ? 'charging' : 'discharging';
|
||||
}),
|
||||
}),
|
||||
Label({
|
||||
className: 'txt-smallie txt-onSurfaceVariant',
|
||||
setup: (self) => self.hook(Battery, label => {
|
||||
label.label = `${Battery.percent}%`;
|
||||
}),
|
||||
}),
|
||||
Overlay({
|
||||
child: Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'bar-batt',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon('settings_heart', 'small'),
|
||||
],
|
||||
setup: (self) => self.hook(Battery, box => {
|
||||
box.toggleClassName('bar-batt-low', Battery.percent <= BATTERY_LOW);
|
||||
box.toggleClassName('bar-batt-full', Battery.charged);
|
||||
}),
|
||||
}),
|
||||
overlays: [
|
||||
BatBatteryProgress(),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
||||
export const ModuleSystem = () => Widget.EventBox({
|
||||
onScrollUp: () => execAsync('hyprctl dispatch workspace -1'),
|
||||
onScrollDown: () => execAsync('hyprctl dispatch workspace +1'),
|
||||
onPrimaryClick: () => App.toggleWindow('sideright'),
|
||||
child: Widget.Box({
|
||||
className: 'bar-group-margin bar-sides',
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: 'bar-group bar-group-standalone bar-group-pad-system spacing-h-15',
|
||||
className: 'bar-group bar-group-standalone bar-group-pad-system spacing-h-5',
|
||||
children: [
|
||||
BarClock(),
|
||||
Utilities(),
|
||||
BarBattery(),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -10,7 +10,13 @@ const SysTrayItem = item => Button({
|
||||
className: 'bar-systray-item',
|
||||
child: Icon({
|
||||
hpack: 'center',
|
||||
binds: [['icon', item, 'icon']]
|
||||
binds: [['icon', item, 'icon']],
|
||||
setup: (self) => Utils.timeout(1, () => {
|
||||
const styleContext = self.get_parent().get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1); // im too lazy to add another box lol
|
||||
}),
|
||||
}),
|
||||
binds: [['tooltipMarkup', item, 'tooltip-markup']],
|
||||
onClicked: btn => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
|
||||
@@ -19,8 +25,7 @@ const SysTrayItem = item => Button({
|
||||
|
||||
export const Tray = (props = {}) => {
|
||||
const trayContent = Box({
|
||||
vpack: 'center',
|
||||
className: 'bar-systray bar-group',
|
||||
className: 'bar-systray spacing-h-10',
|
||||
properties: [
|
||||
['items', new Map()],
|
||||
['onAdded', (box, id) => {
|
||||
@@ -31,7 +36,7 @@ export const Tray = (props = {}) => {
|
||||
return;
|
||||
const widget = SysTrayItem(item);
|
||||
box._items.set(id, widget);
|
||||
box.pack_start(widget, false, false, 0);
|
||||
box.add(widget);
|
||||
box.show_all();
|
||||
if (box._items.size === 1)
|
||||
trayRevealer.revealChild = true;
|
||||
@@ -46,10 +51,10 @@ export const Tray = (props = {}) => {
|
||||
trayRevealer.revealChild = false;
|
||||
}],
|
||||
],
|
||||
connections: [
|
||||
[SystemTray, (box, id) => box._onAdded(box, id), 'added'],
|
||||
[SystemTray, (box, id) => box._onRemoved(box, id), 'removed'],
|
||||
],
|
||||
setup: (self) => self
|
||||
.hook(SystemTray, (box, id) => box._onAdded(box, id), 'added')
|
||||
.hook(SystemTray, (box, id) => box._onRemoved(box, id), 'removed')
|
||||
,
|
||||
});
|
||||
const trayRevealer = Widget.Revealer({
|
||||
revealChild: false,
|
||||
|
||||
@@ -1,100 +1,169 @@
|
||||
const { GLib, Gdk, Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
const Cairo = imports.cairo;
|
||||
const Pango = imports.gi.Pango;
|
||||
const PangoCairo = imports.gi.PangoCairo;
|
||||
import { App, Service, Utils, Widget } from '../../imports.js';
|
||||
const { Box, DrawingArea, EventBox } = Widget;
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
|
||||
const WORKSPACE_SIDE_PAD = 0.546; // rem
|
||||
const NUM_OF_WORKSPACES = 10;
|
||||
let lastWorkspace = 0;
|
||||
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
|
||||
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
|
||||
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
|
||||
|
||||
const activeWorkspaceIndicator = Widget.Box({
|
||||
css: `
|
||||
padding: 0rem ${WORKSPACE_SIDE_PAD}rem;
|
||||
`,
|
||||
children: [
|
||||
Widget.Box({
|
||||
vpack: 'center',
|
||||
hpack: 'start',
|
||||
className: 'bar-ws-active-box',
|
||||
connections: [
|
||||
[Hyprland.active.workspace, (box) => {
|
||||
const ws = Hyprland.active.workspace.id;
|
||||
box.setCss(`
|
||||
margin-left: ${1.774 * (ws - 1) + 0.068}rem;
|
||||
`);
|
||||
lastWorkspace = ws;
|
||||
}],
|
||||
],
|
||||
children: [
|
||||
Widget.Label({
|
||||
vpack: 'center',
|
||||
className: 'bar-ws-active',
|
||||
label: `•`,
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
// Font size = workspace id
|
||||
const WorkspaceContents = (count = 10) => {
|
||||
return DrawingArea({
|
||||
properties: [
|
||||
['workspaceMask', 0],
|
||||
],
|
||||
css: `transition: 500ms cubic-bezier(0.1, 1, 0, 1);`,
|
||||
setup: (area) => area
|
||||
.hook(Hyprland.active.workspace, (area) =>
|
||||
area.setCss(`font-size: ${Hyprland.active.workspace.id}px;`)
|
||||
)
|
||||
.hook(Hyprland, (area) => {
|
||||
const workspaces = Hyprland.workspaces;
|
||||
let workspaceMask = 0;
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
const ws = workspaces[i];
|
||||
if (ws.id < 0) continue; // Ignore scratchpads
|
||||
if (ws.id > count) return; // Not rendered
|
||||
if (workspaces[i].windows > 0) {
|
||||
workspaceMask |= (1 << ws.id);
|
||||
}
|
||||
}
|
||||
area._workspaceMask = workspaceMask;
|
||||
}, 'notify::workspaces')
|
||||
.on('draw', Lang.bind(area, (area, cr) => {
|
||||
const allocation = area.get_allocation();
|
||||
const { width, height } = allocation;
|
||||
|
||||
export const ModuleWorkspaces = () => Widget.EventBox({
|
||||
onScrollUp: () => Utils.execAsync(['bash', '-c', 'hyprctl dispatch workspace -1 &']),
|
||||
onScrollDown: () => Utils.execAsync(['bash', '-c', 'hyprctl dispatch workspace +1 &']),
|
||||
const workspaceStyleContext = dummyWs.get_style_context();
|
||||
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const workspaceRadius = workspaceDiameter / 2;
|
||||
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
|
||||
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
|
||||
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
|
||||
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
|
||||
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
area.set_size_request(workspaceDiameter * count, -1);
|
||||
const widgetStyleContext = area.get_style_context();
|
||||
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
|
||||
const activeWsCenterY = height / 2;
|
||||
|
||||
// Font
|
||||
const layout = PangoCairo.create_layout(cr);
|
||||
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
|
||||
layout.set_font_description(fontDesc);
|
||||
cr.setAntialias(Cairo.Antialias.BEST);
|
||||
// Get kinda min radius for number indicators
|
||||
layout.set_text("0".repeat(count.toString().length), -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
|
||||
const indicatorGap = workspaceRadius - indicatorRadius;
|
||||
|
||||
// Draw workspace numbers
|
||||
for (let i = 1; i <= count; i++) {
|
||||
if (area._workspaceMask & (1 << i)) {
|
||||
// Draw bg highlight
|
||||
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
|
||||
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
|
||||
const wsCenterY = height / 2;
|
||||
if (!(area._workspaceMask & (1 << (i - 1)))) { // Left
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
if (!(area._workspaceMask & (1 << (i + 1)))) { // Right
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
|
||||
// Set color for text
|
||||
cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
|
||||
}
|
||||
else
|
||||
cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
|
||||
layout.set_text(`${i}`, -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
|
||||
const y = (height - layoutHeight) / 2;
|
||||
cr.moveTo(x, y);
|
||||
// cr.showText(text);
|
||||
PangoCairo.show_layout(cr, layout);
|
||||
cr.stroke();
|
||||
}
|
||||
|
||||
// Draw active ws
|
||||
// base
|
||||
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
|
||||
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
// inner decor
|
||||
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
|
||||
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
}))
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
export default () => EventBox({
|
||||
onScrollUp: () => Hyprland.sendMessage(`dispatch workspace -1`),
|
||||
onScrollDown: () => Hyprland.sendMessage(`dispatch workspace +1`),
|
||||
onMiddleClickRelease: () => App.toggleWindow('overview'),
|
||||
onSecondaryClickRelease: () => App.toggleWindow('osk'),
|
||||
child: Widget.Box({
|
||||
properties: [
|
||||
['clicked', false],
|
||||
],
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
className: 'bar-ws-width',
|
||||
children: [
|
||||
Widget.Overlay({
|
||||
passThrough: true,
|
||||
child: Widget.Box({
|
||||
homogeneous: true,
|
||||
className: 'bar-group-center',
|
||||
children: [Widget.Box({
|
||||
className: 'bar-group-standalone bar-group-pad',
|
||||
})]
|
||||
}),
|
||||
overlays: [
|
||||
Widget.Overlay({
|
||||
setup: (self) => self.set_overlay_pass_through(self.get_children()[1], true),
|
||||
child: Widget.Box({
|
||||
hpack: 'center',
|
||||
css: `
|
||||
padding: 0rem ${WORKSPACE_SIDE_PAD}rem;
|
||||
`,
|
||||
// homogeneous: true,
|
||||
children: Array.from({ length: NUM_OF_WORKSPACES }, (_, i) => i + 1).map(i => Widget.Button({
|
||||
onPrimaryClick: () => Utils.execAsync(['bash', '-c', `hyprctl dispatch workspace ${i} &`]).catch(print),
|
||||
child: Widget.Label({
|
||||
vpack: 'center',
|
||||
label: `${i}`,
|
||||
className: 'bar-ws txt',
|
||||
}),
|
||||
})),
|
||||
connections: [
|
||||
[Hyprland, (box) => {
|
||||
// console.log('update');
|
||||
const kids = box.children;
|
||||
kids.forEach((child, i) => {
|
||||
child.child.toggleClassName('bar-ws-occupied', false);
|
||||
child.child.toggleClassName('bar-ws-occupied-left', false);
|
||||
child.child.toggleClassName('bar-ws-occupied-right', false);
|
||||
child.child.toggleClassName('bar-ws-occupied-left-right', false);
|
||||
});
|
||||
const occupied = Array.from({ length: NUM_OF_WORKSPACES }, (_, i) => Hyprland.getWorkspace(i + 1)?.windows > 0);
|
||||
for (let i = 0; i < occupied.length; i++) {
|
||||
if (!occupied[i]) continue;
|
||||
const child = kids[i];
|
||||
child.child.toggleClassName(`bar-ws-occupied${!occupied[i - 1] ? '-left' : ''}${!occupied[i + 1] ? '-right' : ''}`, true);
|
||||
}
|
||||
}, 'notify::workspaces'],
|
||||
],
|
||||
}),
|
||||
overlays: [
|
||||
activeWorkspaceIndicator,
|
||||
]
|
||||
})
|
||||
],
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
className: 'bar-group-margin',
|
||||
children: [Box({
|
||||
className: 'bar-group bar-group-standalone bar-group-pad',
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
WorkspaceContents(10),
|
||||
]
|
||||
})]
|
||||
}),
|
||||
setup: (self) => {
|
||||
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
|
||||
self.on('motion-notify-event', (self, event) => {
|
||||
if (!self._clicked) return;
|
||||
console.log('switching move');
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth);
|
||||
Hyprland.sendMessage(`dispatch workspace ${wsId}`)
|
||||
})
|
||||
self.on('button-press-event', (self, event) => {
|
||||
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
|
||||
console.log('switching');
|
||||
self._clicked = true;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth);
|
||||
Hyprland.sendMessage(`dispatch workspace ${wsId}`);
|
||||
})
|
||||
self.on('button-release-event', (self) => self._clicked = false);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -69,7 +69,7 @@ const clickOutsideToClose = Widget.EventBox({
|
||||
|
||||
export default () => Widget.Window({
|
||||
name: 'cheatsheet',
|
||||
exclusivity: 'normal',
|
||||
exclusivity: 'ignore',
|
||||
focusable: true,
|
||||
popup: true,
|
||||
visible: false,
|
||||
|
||||
@@ -138,10 +138,12 @@ export default () => Box({
|
||||
}),
|
||||
onPrimaryClickRelease: () => {
|
||||
const kids = resources.get_children();
|
||||
|
||||
kids.forEach((child, i) => {
|
||||
child.get_children()[0].revealChild = !child.get_children()[0].revealChild;
|
||||
});
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
const child = kids[i];
|
||||
const firstChild = child.get_children()[0];
|
||||
firstChild.revealChild = !firstChild.revealChild;
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
],
|
||||
|
||||
@@ -14,16 +14,18 @@ const TimeAndDate = () => Box({
|
||||
Label({
|
||||
className: 'bg-time-clock',
|
||||
xalign: 0,
|
||||
connections: [[5000, label => {
|
||||
label: GLib.DateTime.new_now_local().format("%H:%M"),
|
||||
setup: (self) => self.poll(5000, label => {
|
||||
label.label = GLib.DateTime.new_now_local().format("%H:%M");
|
||||
}]],
|
||||
}),
|
||||
}),
|
||||
Label({
|
||||
className: 'bg-time-date',
|
||||
xalign: 0,
|
||||
connections: [[5000, label => {
|
||||
label: GLib.DateTime.new_now_local().format("%A, %d/%m/%Y"),
|
||||
setup: (self) => self.poll(5000, label => {
|
||||
label.label = GLib.DateTime.new_now_local().format("%A, %d/%m/%Y");
|
||||
}]],
|
||||
}),
|
||||
}),
|
||||
]
|
||||
})
|
||||
@@ -46,7 +48,7 @@ const QuickLaunches = () => Box({
|
||||
},
|
||||
className: 'bg-quicklaunch-btn',
|
||||
child: Label({
|
||||
label: `${ item["name"]}`,
|
||||
label: `${item["name"]}`,
|
||||
}),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
@@ -60,7 +62,7 @@ export default () => Box({
|
||||
hpack: 'start',
|
||||
vpack: 'end',
|
||||
vertical: true,
|
||||
className: 'bg-time-box spacing-v-20',
|
||||
className: 'bg-time-box spacing-h--10',
|
||||
children: [
|
||||
TimeAndDate(),
|
||||
// QuickLaunches(),
|
||||
|
||||
@@ -86,7 +86,8 @@ const Taskbar = () => Widget.Box({
|
||||
return a._workspace > b._workspace;
|
||||
}],
|
||||
['update', (box) => {
|
||||
Hyprland.clients.forEach(client => {
|
||||
for (let i = 0; i < Hyprland.clients.length; i++) {
|
||||
const client = Hyprland.clients[i];
|
||||
if (client["pid"] == -1) return;
|
||||
const appClass = substitute(client.class);
|
||||
for (const appName of pinnedApps) {
|
||||
@@ -101,11 +102,11 @@ const Taskbar = () => Widget.Box({
|
||||
newButton._workspace = client.workspace.id;
|
||||
newButton.revealChild = true;
|
||||
box._map.set(client.address, newButton);
|
||||
})
|
||||
}
|
||||
box.children = Array.from(box._map.values());
|
||||
}],
|
||||
['add', (box, address) => {
|
||||
if (!address) { // Since the first active emit is undefined
|
||||
if (!address) { // First active emit is undefined
|
||||
box._update(box);
|
||||
return;
|
||||
}
|
||||
@@ -137,14 +138,11 @@ const Taskbar = () => Widget.Box({
|
||||
})
|
||||
}],
|
||||
],
|
||||
connections: [
|
||||
// [Hyprland, (box) => box._update(box)],
|
||||
[Hyprland, (box, address) => box._add(box, address), 'client-added'],
|
||||
[Hyprland, (box, address) => box._remove(box, address), 'client-removed'],
|
||||
],
|
||||
setup: (self) => {
|
||||
self.hook(Hyprland, (box, address) => box._add(box, address), 'client-added')
|
||||
.hook(Hyprland, (box, address) => box._remove(box, address), 'client-removed')
|
||||
Utils.timeout(100, () => self._update(self));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const PinnedApps = () => Widget.Box({
|
||||
@@ -165,18 +163,18 @@ const PinnedApps = () => Widget.Box({
|
||||
app.launch();
|
||||
},
|
||||
onMiddleClick: () => app.launch(),
|
||||
tooltipText: app.name,
|
||||
setup: (self) => {
|
||||
self.revealChild = true;
|
||||
},
|
||||
tooltipText: app.name,
|
||||
connections: [[Hyprland, button => {
|
||||
const running = Hyprland.clients
|
||||
.find(client => client.class.toLowerCase().includes(term)) || false;
|
||||
self.hook(Hyprland, button => {
|
||||
const running = Hyprland.clients
|
||||
.find(client => client.class.toLowerCase().includes(term)) || false;
|
||||
|
||||
button.toggleClassName('nonrunning', !running);
|
||||
button.toggleClassName('focused', Hyprland.active.client.address == running.address);
|
||||
button.set_tooltip_text(running ? running.title : app.name);
|
||||
}, 'notify::clients']],
|
||||
button.toggleClassName('notrunning', !running);
|
||||
button.toggleClassName('focused', Hyprland.active.client.address == running.address);
|
||||
button.set_tooltip_text(running ? running.title : app.name);
|
||||
}, 'notify::clients')
|
||||
},
|
||||
})
|
||||
newButton.revealChild = true;
|
||||
return newButton;
|
||||
@@ -237,13 +235,13 @@ export default () => {
|
||||
transition: 'slide_up',
|
||||
transitionDuration: 200,
|
||||
child: dockContent,
|
||||
connections: [
|
||||
// [Hyprland, (self) => self._updateShow(self)],
|
||||
// [Hyprland.active.workspace, (self) => self._updateShow(self)],
|
||||
// [Hyprland.active.client, (self) => self._updateShow(self)],
|
||||
// [Hyprland, (self) => self._updateShow(self), 'client-added'],
|
||||
// [Hyprland, (self) => self._updateShow(self), 'client-removed'],
|
||||
],
|
||||
// setup: (self) => self
|
||||
// .hook(Hyprland, (self) => self._updateShow(self))
|
||||
// .hook(Hyprland.active.workspace, (self) => self._updateShow(self))
|
||||
// .hook(Hyprland.active.client, (self) => self._updateShow(self))
|
||||
// .hook(Hyprland, (self) => self._updateShow(self), 'client-added')
|
||||
// .hook(Hyprland, (self) => self._updateShow(self), 'client-removed')
|
||||
// ,
|
||||
})
|
||||
return EventBox({
|
||||
onHover: () => {
|
||||
|
||||
@@ -50,9 +50,7 @@ export default () => Widget.Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 200,
|
||||
child: colorschemeContent,
|
||||
connections: [
|
||||
[showColorScheme, (revealer) => {
|
||||
revealer.revealChild = showColorScheme.value;
|
||||
}],
|
||||
],
|
||||
setup: (self) => self.hook(showColorScheme, (revealer) => {
|
||||
revealer.revealChild = showColorScheme.value;
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -2,34 +2,33 @@
|
||||
const { GLib, Gtk } = imports.gi;
|
||||
import { App, Service, Utils, Widget } from '../../imports.js';
|
||||
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
|
||||
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
|
||||
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
|
||||
const { Box, Label, ProgressBar, Revealer } = Widget;
|
||||
import { MarginRevealer } from '../../lib/advancedrevealers.js';
|
||||
import Brightness from '../../services/brightness.js';
|
||||
import Indicator from '../../services/indicator.js';
|
||||
import Notification from '../../lib/notification.js';
|
||||
|
||||
const OsdValue = (name, labelConnections, progressConnections, props = {}) => Widget.Box({ // Volume
|
||||
const OsdValue = (name, labelConnections, progressConnections, props = {}) => Box({ // Volume
|
||||
...props,
|
||||
vertical: true,
|
||||
className: 'osd-bg osd-value',
|
||||
hexpand: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
Box({
|
||||
vexpand: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
Label({
|
||||
xalign: 0, yalign: 0, hexpand: true,
|
||||
className: 'osd-label',
|
||||
label: `${name}`,
|
||||
}),
|
||||
Widget.Label({
|
||||
Label({
|
||||
hexpand: false, className: 'osd-value-txt',
|
||||
label: '100',
|
||||
connections: labelConnections,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
Widget.ProgressBar({
|
||||
ProgressBar({
|
||||
className: 'osd-progress',
|
||||
hexpand: true,
|
||||
vertical: false,
|
||||
@@ -58,16 +57,20 @@ const volumeIndicator = OsdValue('Volume',
|
||||
}]],
|
||||
);
|
||||
|
||||
export default () => Widget.Revealer({
|
||||
export default () => MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
showClass: 'osd-show',
|
||||
hideClass: 'osd-hide',
|
||||
connections: [
|
||||
[Indicator, (revealer, value) => {
|
||||
revealer.revealChild = (value > -1);
|
||||
if(value > -1) revealer._show(revealer);
|
||||
else revealer._hide(revealer);
|
||||
}, 'popup'],
|
||||
],
|
||||
child: Widget.Box({
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
vertical: false,
|
||||
className: 'spacing-h--10',
|
||||
children: [
|
||||
brightnessIndicator,
|
||||
volumeIndicator,
|
||||
|
||||
@@ -10,6 +10,7 @@ export default (monitor) => Widget.Window({
|
||||
monitor,
|
||||
className: 'indicator',
|
||||
layer: 'overlay',
|
||||
// exclusivity: 'ignore',
|
||||
visible: true,
|
||||
anchor: ['top'],
|
||||
child: Widget.EventBox({
|
||||
@@ -18,6 +19,7 @@ export default (monitor) => Widget.Window({
|
||||
},
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'osd-window',
|
||||
css: 'min-height: 2px;',
|
||||
children: [
|
||||
IndicatorValues(),
|
||||
@@ -28,3 +30,4 @@ export default (monitor) => Widget.Window({
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const { exec, execAsync } = Utils;
|
||||
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
|
||||
|
||||
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
|
||||
import { MarginRevealer } from '../../lib/advancedrevealers.js';
|
||||
import { AnimatedCircProg } from "../../lib/animatedcircularprogress.js";
|
||||
import { MaterialIcon } from '../../lib/materialicon.js';
|
||||
import { showMusicControls } from '../../variables.js';
|
||||
@@ -343,9 +344,11 @@ const MusicControlsWidget = (player) => Box({
|
||||
]
|
||||
})
|
||||
|
||||
export default () => Widget.Revealer({
|
||||
export default () => MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 170,
|
||||
revealChild: false,
|
||||
showClass: 'osd-show',
|
||||
hideClass: 'osd-hide',
|
||||
child: Box({
|
||||
connections: [[Mpris, box => {
|
||||
let foundPlayer = false;
|
||||
@@ -360,14 +363,19 @@ export default () => Widget.Revealer({
|
||||
|
||||
if (!foundPlayer) {
|
||||
box._player = null;
|
||||
box.get_children().forEach(ch => ch.destroy());
|
||||
const children = box.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, 'notify::players']],
|
||||
}),
|
||||
connections: [
|
||||
[showMusicControls, (revealer) => {
|
||||
revealer.revealChild = showMusicControls.value;
|
||||
if(showMusicControls.value) revealer._show(revealer);
|
||||
else revealer._hide(revealer);
|
||||
}],
|
||||
],
|
||||
})
|
||||
|
||||
@@ -30,12 +30,10 @@ const PopupNotification = (notifObject) => Widget.Box({
|
||||
const naiveNotifPopupList = Widget.Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
connections: [
|
||||
[Notifications, (box) => {
|
||||
box.children = Notifications.popups.reverse()
|
||||
.map(notifItem => PopupNotification(notifItem));
|
||||
}],
|
||||
],
|
||||
setup: (self) => self.hook(Notifications, (box) => {
|
||||
box.children = Notifications.popups.reverse()
|
||||
.map(notifItem => PopupNotification(notifItem));
|
||||
}),
|
||||
})
|
||||
|
||||
const notifPopupList = Box({
|
||||
@@ -72,11 +70,11 @@ const notifPopupList = Box({
|
||||
// box.children = Array.from(box._map.values()).reverse();
|
||||
}],
|
||||
],
|
||||
connections: [
|
||||
[Notifications, (box, id) => box._notify(box, id), 'notified'],
|
||||
[Notifications, (box, id) => box._dismiss(box, id), 'dismissed'],
|
||||
[Notifications, (box, id) => box._dismiss(box, id, true), 'closed'],
|
||||
],
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (box, id) => box._notify(box, id), 'notified')
|
||||
.hook(Notifications, (box, id) => box._dismiss(box, id), 'dismissed')
|
||||
.hook(Notifications, (box, id) => box._dismiss(box, id, true), 'closed')
|
||||
,
|
||||
});
|
||||
|
||||
export default () => notifPopupList;
|
||||
@@ -133,11 +133,11 @@ const keyboardWindow = Box({
|
||||
],
|
||||
})
|
||||
],
|
||||
connections: [[App, (box, name, visible) => { // Update on open
|
||||
setup: (self) => self.hook(App, (box, name, visible) => { // Update on open
|
||||
if (name == 'osk' && visible) {
|
||||
keyboardWindow.setCss(`margin-bottom: -0px;`);
|
||||
}
|
||||
}],],
|
||||
}),
|
||||
});
|
||||
|
||||
const gestureEvBox = EventBox({ child: keyboardWindow })
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SearchAndWindows } from "./overview.js";
|
||||
|
||||
export default () => Widget.Window({
|
||||
name: 'overview',
|
||||
exclusivity: 'normal',
|
||||
exclusivity: 'ignore',
|
||||
focusable: true,
|
||||
popup: true,
|
||||
visible: false,
|
||||
|
||||
@@ -4,7 +4,7 @@ import Applications from 'resource:///com/github/Aylur/ags/service/applications.
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
import { setupCursorHover, setupCursorHoverGrab } from "../../lib/cursorhover.js";
|
||||
import { DoubleRevealer } from "../../lib/doublerevealer.js";
|
||||
import { DoubleRevealer } from "../../lib/advancedrevealers.js";
|
||||
import { execAndClose, expandTilde, hasUnterminatedBackslash, startsWithNumber, launchCustomCommand, ls } from './miscfunctions.js';
|
||||
import {
|
||||
CalculationResultButton, CustomCommandButton, DirectoryButton,
|
||||
@@ -257,7 +257,7 @@ const workspace = index => {
|
||||
child: fixed,
|
||||
})],
|
||||
});
|
||||
widget.update = clients => {
|
||||
widget.update = (clients) => {
|
||||
clients = clients.filter(({ workspace: { id } }) => id === index);
|
||||
|
||||
// this is for my monitor layout
|
||||
@@ -269,9 +269,22 @@ const workspace = index => {
|
||||
return client;
|
||||
});
|
||||
|
||||
fixed.get_children().forEach(ch => ch.destroy());
|
||||
const children = fixed.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
fixed.put(WorkspaceNumber(index), 0, 0);
|
||||
clients.forEach(c => c.mapped && fixed.put(client(c), c.at[0] * OVERVIEW_SCALE, c.at[1] * OVERVIEW_SCALE));
|
||||
|
||||
for (let i = 0; i < clients.length; i++) {
|
||||
const c = clients[i];
|
||||
if (c.mapped) {
|
||||
fixed.put(client(c), c.at[0] * OVERVIEW_SCALE, c.at[1] * OVERVIEW_SCALE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fixed.show_all();
|
||||
};
|
||||
return widget;
|
||||
@@ -290,7 +303,12 @@ const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) =>
|
||||
properties: [['update', box => {
|
||||
execAsync('hyprctl -j clients').then(clients => {
|
||||
const json = JSON.parse(clients);
|
||||
box.get_children().forEach(ch => ch.update(json));
|
||||
const children = box.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const ch = children[i];
|
||||
ch.update(json)
|
||||
}
|
||||
|
||||
}).catch(print);
|
||||
}]],
|
||||
setup: (box) => box._update(box),
|
||||
@@ -312,7 +330,8 @@ const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) =>
|
||||
export const SearchAndWindows = () => {
|
||||
var _appSearchResults = [];
|
||||
|
||||
const clickOutsideToClose = Widget.EventBox({
|
||||
const ClickToClose = ({ ...props }) => Widget.EventBox({
|
||||
...props,
|
||||
onPrimaryClick: () => App.closeWindow('overview'),
|
||||
onSecondaryClick: () => App.closeWindow('overview'),
|
||||
onMiddleClick: () => App.closeWindow('overview'),
|
||||
@@ -375,7 +394,7 @@ export const SearchAndWindows = () => {
|
||||
hpack: 'center',
|
||||
onAccept: (self) => { // This is when you hit Enter
|
||||
const text = self.text;
|
||||
if(text.length == 0) return;
|
||||
if (text.length == 0) return;
|
||||
const isAction = text.startsWith('>');
|
||||
const isDir = (entry.text[0] == '/' || entry.text[0] == '~');
|
||||
|
||||
@@ -423,7 +442,11 @@ export const SearchAndWindows = () => {
|
||||
['notify::text', (entry) => { // This is when you type
|
||||
const isAction = entry.text[0] == '>';
|
||||
const isDir = (entry.text[0] == '/' || entry.text[0] == '~');
|
||||
resultsBox.get_children().forEach(ch => ch.destroy());
|
||||
const children = resultsBox.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
// check empty if so then dont do stuff
|
||||
if (entry.text == '') {
|
||||
resultsRevealer.set_reveal_child(false);
|
||||
@@ -485,7 +508,11 @@ export const SearchAndWindows = () => {
|
||||
return Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
clickOutsideToClose,
|
||||
ClickToClose({ // Top margin. Also works as a click-outside-to-close thing
|
||||
child: Widget.Box({
|
||||
className: 'bar-height',
|
||||
})
|
||||
}),
|
||||
Widget.Box({
|
||||
hpack: 'center',
|
||||
children: [
|
||||
|
||||
@@ -21,7 +21,7 @@ export const CornerBottomleft = () => Widget.Window({
|
||||
name: 'cornerbl',
|
||||
layer: 'top',
|
||||
anchor: ['bottom', 'left'],
|
||||
exclusivity: 'normal',
|
||||
exclusivity: 'ignore',
|
||||
visible: true,
|
||||
child: RoundedCorner('bottomleft', { className: 'corner-black', }),
|
||||
});
|
||||
@@ -29,7 +29,7 @@ export const CornerBottomright = () => Widget.Window({
|
||||
name: 'cornerbr',
|
||||
layer: 'top',
|
||||
anchor: ['bottom', 'right'],
|
||||
exclusivity: 'normal',
|
||||
exclusivity: 'ignore',
|
||||
visible: true,
|
||||
child: RoundedCorner('bottomright', { className: 'corner-black', }),
|
||||
});
|
||||
|
||||
@@ -6,22 +6,20 @@ import ChatGPT from '../../../services/chatgpt.js';
|
||||
import { MaterialIcon } from "../../../lib/materialicon.js";
|
||||
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
|
||||
import { SystemMessage, ChatMessage } from "./chatgpt_chatmessage.js";
|
||||
import { ConfigToggle } from '../../../lib/configwidgets.js';
|
||||
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
|
||||
import { markdownTest } from '../../../lib/md2pango.js';
|
||||
import { MarginRevealer } from '../../../lib/advancedrevealers.js';
|
||||
|
||||
const chatGPTTabIcon = Icon({
|
||||
export const chatGPTTabIcon = Box({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
icon: `${App.configDir}/assets/openai-logomark.svg`,
|
||||
setup: (self) => Utils.timeout(1, () => {
|
||||
const styleContext = self.get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1) * 116 / 180; // Why such a specific proportion? See https://openai.com/brand#logos
|
||||
})
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon('forum', 'norm'),
|
||||
],
|
||||
});
|
||||
|
||||
export const chatGPTInfo = Box({
|
||||
const chatGPTInfo = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
@@ -63,41 +61,62 @@ export const chatGPTInfo = Box({
|
||||
]
|
||||
})
|
||||
|
||||
export const chatGPTSettings = Revealer({
|
||||
export const chatGPTSettings = MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 150,
|
||||
revealChild: true,
|
||||
connections: [
|
||||
[ChatGPT, (self) => {
|
||||
self.revealChild = false;
|
||||
}, 'newMsg'],
|
||||
[ChatGPT, (self) => {
|
||||
self.revealChild = true;
|
||||
}, 'clear'],
|
||||
[ChatGPT, (self) => Utils.timeout(200, () => {
|
||||
self._hide(self);
|
||||
}), 'newMsg'],
|
||||
[ChatGPT, (self) => Utils.timeout(200, () => {
|
||||
self._show(self);
|
||||
}), 'clear'],
|
||||
],
|
||||
child: Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'cycle',
|
||||
name: 'Cycle models',
|
||||
desc: 'Helps avoid exceeding the API rate of 3 messages per minute.\nTurn this on if you message rapidly.',
|
||||
initValue: ChatGPT.cycleModels,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.cycleModels = newValue;
|
||||
},
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'description',
|
||||
name: 'Assistant prompt',
|
||||
desc: 'Tells ChatGPT\n 1. It\'s a sidebar assistant on Linux\n 2. Be short and concise\n 3. Use markdown features extensively\nLeave this off for a vanilla ChatGPT experience.',
|
||||
initValue: ChatGPT.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.assistantPrompt = newValue;
|
||||
ConfigSegmentedSelection({
|
||||
hpack: 'center',
|
||||
icon: 'casino',
|
||||
name: 'Randomness',
|
||||
desc: 'ChatGPT\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
|
||||
options: [
|
||||
{ value: 0.00, name: 'Precise', },
|
||||
{ value: 0.50, name: 'Balanced', },
|
||||
{ value: 1.00, name: 'Creative', },
|
||||
],
|
||||
initIndex: 1,
|
||||
onChange: (value, name) => {
|
||||
ChatGPT.temperature = value;
|
||||
},
|
||||
}),
|
||||
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings-toggles',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'cycle',
|
||||
name: 'Cycle models',
|
||||
desc: 'Helps avoid exceeding the API rate of 3 messages per minute.\nTurn this on if you message rapidly.',
|
||||
initValue: ChatGPT.cycleModels,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.cycleModels = newValue;
|
||||
},
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'description',
|
||||
name: 'Assistant prompt',
|
||||
desc: 'Tells ChatGPT\n 1. It\'s a sidebar assistant on Linux\n 2. Be short and concise\n 3. Use markdown features extensively\nLeave this off for a vanilla ChatGPT experience.',
|
||||
initValue: ChatGPT.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.assistantPrompt = newValue;
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -136,7 +155,7 @@ export const chatGPTWelcome = Box({
|
||||
children: [
|
||||
chatGPTInfo,
|
||||
openaiApiKeyInstructions,
|
||||
chatGPTSettings,
|
||||
chatGPTSettings, ``
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -148,27 +167,37 @@ export const chatContent = Box({
|
||||
[ChatGPT, (box, id) => {
|
||||
const message = ChatGPT.messages[id];
|
||||
if (!message) return;
|
||||
box.add(ChatMessage(message))
|
||||
box.add(ChatMessage(message, chatGPTView))
|
||||
}, 'newMsg'],
|
||||
[ChatGPT, (box) => {
|
||||
box.children = [chatGPTWelcome];
|
||||
}, 'clear'],
|
||||
[ChatGPT, (box) => {
|
||||
box.children = [chatGPTWelcome];
|
||||
}, 'initialized'],
|
||||
]
|
||||
});
|
||||
|
||||
const clearChat = () => {
|
||||
ChatGPT.clear();
|
||||
const children = chatContent.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const chatGPTView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: chatContent,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
chatGPTWelcome,
|
||||
chatContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
|
||||
Utils.timeout(1, () => { // Fix click-to-scroll-widget-to-view behavior
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
@@ -183,7 +212,8 @@ export const chatGPTCommands = Box({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => chatContent.add(SystemMessage(
|
||||
`Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
|
||||
'/key')),
|
||||
'/key',
|
||||
chatGPTView)),
|
||||
setup: setupCursorHover,
|
||||
label: '/key',
|
||||
}),
|
||||
@@ -191,14 +221,15 @@ export const chatGPTCommands = Box({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => chatContent.add(SystemMessage(
|
||||
`Currently using \`${ChatGPT.modelName}\``,
|
||||
'/model'
|
||||
'/model',
|
||||
chatGPTView
|
||||
)),
|
||||
setup: setupCursorHover,
|
||||
label: '/model',
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => ChatGPT.clear(),
|
||||
onClicked: () => clearChat(),
|
||||
setup: setupCursorHover,
|
||||
label: '/clear',
|
||||
}),
|
||||
@@ -210,26 +241,39 @@ export const chatGPTSendMessage = (text) => {
|
||||
if (text.length == 0) return;
|
||||
if (ChatGPT.key.length == 0) {
|
||||
ChatGPT.key = text;
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${ChatGPT.keyPath}\``, 'API Key'));
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${ChatGPT.keyPath}\``, 'API Key', chatGPTView));
|
||||
text = '';
|
||||
return;
|
||||
}
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) ChatGPT.clear();
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${ChatGPT.modelName}\``, '/model'))
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${ChatGPT.modelName}\``, '/model', chatGPTView))
|
||||
else if (text.startsWith('/prompt')) {
|
||||
const firstSpaceIndex = text.indexOf(' ');
|
||||
const prompt = text.slice(firstSpaceIndex + 1);
|
||||
if (firstSpaceIndex == -1 || prompt.length < 1) {
|
||||
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView))
|
||||
}
|
||||
else {
|
||||
ChatGPT.addMessage('user', prompt)
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/key')) {
|
||||
const parts = text.split(' ');
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(`Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``, '/key'));
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(
|
||||
`Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
|
||||
'/key',
|
||||
chatGPTView));
|
||||
else {
|
||||
ChatGPT.key = parts[1];
|
||||
chatContent.add(SystemMessage(`Updated API Key at\n\`${ChatGPT.keyPath}\``, '/key'));
|
||||
chatContent.add(SystemMessage(`Updated API Key at\n\`${ChatGPT.keyPath}\``, '/key', chatGPTView));
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/test'))
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`));
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`, chatGPTView));
|
||||
else
|
||||
chatContent.add(SystemMessage(`Invalid command.`, 'Error'))
|
||||
chatContent.add(SystemMessage(`Invalid command.`, 'Error', chatGPTView))
|
||||
}
|
||||
else {
|
||||
ChatGPT.send(text);
|
||||
|
||||
@@ -9,7 +9,7 @@ import GtkSource from "gi://GtkSource?version=3.0";
|
||||
const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/data/sourceviewtheme.xml`;
|
||||
const CUSTOM_SCHEME_ID = 'custom';
|
||||
const USERNAME = GLib.get_user_name();
|
||||
const CHATGPT_CURSOR = ' >> ';
|
||||
const CHATGPT_CURSOR = ' (o) ';
|
||||
const MESSAGE_SCROLL_DELAY = 13; // In milliseconds, the time before an updated message scrolls to bottom
|
||||
|
||||
/////////////////////// Custom source view colorscheme /////////////////////////
|
||||
@@ -92,10 +92,6 @@ const CodeBlock = (content = '', lang = 'txt') => {
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-chat-codeblock-topbar-btn',
|
||||
onClicked: (self) => {
|
||||
// execAsync(['bash', '-c', `wl-copy '${content}'`, `&`]).catch(print);
|
||||
execAsync([`wl-copy`, `${sourceView.label}`]).catch(print);
|
||||
},
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
@@ -104,8 +100,13 @@ const CodeBlock = (content = '', lang = 'txt') => {
|
||||
label: 'Copy',
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
const copyContent = sourceView.get_buffer().get_text(0, 0, 0); // TODO: fix this
|
||||
console.log(copyContent);
|
||||
execAsync([`wl-copy`, `${copyContent}`]).catch(print);
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
// Source view
|
||||
@@ -148,7 +149,11 @@ const MessageContent = (content) => {
|
||||
properties: [
|
||||
['fullUpdate', (self, content, useCursor = false) => {
|
||||
// Clear and add first text widget
|
||||
contentBox.get_children().forEach(ch => ch.destroy());
|
||||
const children = contentBox.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
contentBox.add(TextBlock())
|
||||
// Loop lines. Put normal text in markdown parser
|
||||
// and put code into code highlighter (TODO)
|
||||
@@ -214,7 +219,7 @@ const MessageContent = (content) => {
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
export const ChatMessage = (message) => {
|
||||
export const ChatMessage = (message, scrolledWindow) => {
|
||||
const messageContentBox = MessageContent(message.content);
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
@@ -243,7 +248,7 @@ export const ChatMessage = (message) => {
|
||||
[message, (self) => { // Message update
|
||||
messageContentBox._fullUpdate(messageContentBox, message.content, message.role != 'user');
|
||||
Utils.timeout(MESSAGE_SCROLL_DELAY, () => {
|
||||
const scrolledWindow = thisMessage.get_parent().get_parent();
|
||||
if (!scrolledWindow) return;
|
||||
var adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
});
|
||||
@@ -258,7 +263,7 @@ export const ChatMessage = (message) => {
|
||||
return thisMessage;
|
||||
}
|
||||
|
||||
export const SystemMessage = (content, commandName) => {
|
||||
export const SystemMessage = (content, commandName, scrolledWindow) => {
|
||||
const messageContentBox = MessageContent(content);
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
@@ -282,7 +287,7 @@ export const SystemMessage = (content, commandName) => {
|
||||
})
|
||||
],
|
||||
setup: (self) => Utils.timeout(MESSAGE_SCROLL_DELAY, () => {
|
||||
const scrolledWindow = thisMessage.get_parent().get_parent();
|
||||
if (!scrolledWindow) return;
|
||||
var adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
const { Gdk, GLib, Gtk, Pango } = imports.gi;
|
||||
import { App, Utils, Widget } from '../../../imports.js';
|
||||
const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from "../../../lib/materialicon.js";
|
||||
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
|
||||
import WaifuService from '../../../services/waifus.js';
|
||||
|
||||
export const waifuTabIcon = Box({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon('photo_library', 'norm'),
|
||||
]
|
||||
});
|
||||
|
||||
const waifuContent = Box({
|
||||
className: 'spacing-v-15',
|
||||
vertical: true,
|
||||
connections: [
|
||||
[WaifuService, (box, id) => {
|
||||
const message = WaifuService.responses[id];
|
||||
if (!message) return;
|
||||
box.add(Label({
|
||||
label: message,
|
||||
}))
|
||||
}, 'newResponse'],
|
||||
]
|
||||
});
|
||||
|
||||
export const waifuView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
waifuContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
export const waifuCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => {
|
||||
// command do something
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
label: '/call',
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
||||
export const waifuCallAPI = (text) => {
|
||||
// Do something on send
|
||||
WaifuService.fetch(text);
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
const { Gtk, Gdk } = imports.gi;
|
||||
import { App, Utils, Widget } from '../../imports.js';
|
||||
const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js";
|
||||
// APIs
|
||||
import ChatGPT from '../../services/chatgpt.js';
|
||||
import { chatGPTView, chatGPTCommands, chatGPTSendMessage } from './apis/chatgpt.js';
|
||||
import { chatGPTView, chatGPTCommands, chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
|
||||
import { waifuView, waifuCommands, waifuCallAPI, waifuTabIcon } from './apis/waifu.js';
|
||||
|
||||
const APIS = [
|
||||
{
|
||||
@@ -12,20 +14,20 @@ const APIS = [
|
||||
sendCommand: chatGPTSendMessage,
|
||||
contentWidget: chatGPTView,
|
||||
commandBar: chatGPTCommands,
|
||||
tabIcon: Box({}),
|
||||
}
|
||||
tabIcon: chatGPTTabIcon,
|
||||
placeholderText: 'Message ChatGPT',
|
||||
},
|
||||
{
|
||||
name: 'Waifus',
|
||||
sendCommand: waifuCallAPI,
|
||||
contentWidget: waifuView,
|
||||
commandBar: waifuCommands,
|
||||
tabIcon: waifuTabIcon,
|
||||
placeholderText: 'Enter tags',
|
||||
},
|
||||
];
|
||||
let currentApiId = 0;
|
||||
|
||||
const apiSwitcher = Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
homogeneous: true,
|
||||
children: APIS.map(api => api.tabIcon),
|
||||
}),
|
||||
]
|
||||
})
|
||||
APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true);
|
||||
|
||||
export const chatEntry = Entry({
|
||||
className: 'sidebar-chat-entry',
|
||||
@@ -75,7 +77,37 @@ const apiCommandStack = Stack({
|
||||
items: APIS.map(api => [api.name, api.commandBar]),
|
||||
})
|
||||
|
||||
function switchToTab(id) {
|
||||
APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', false);
|
||||
APIS[id].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true);
|
||||
apiContentStack.shown = APIS[id].name;
|
||||
apiCommandStack.shown = APIS[id].name;
|
||||
chatEntry.placeholderText = APIS[id].placeholderText,
|
||||
currentApiId = id;
|
||||
}
|
||||
const apiSwitcher = Box({
|
||||
homogeneous: true,
|
||||
children: [
|
||||
Box({
|
||||
className: 'sidebar-chat-apiswitcher spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: APIS.map((api, id) => Button({
|
||||
child: api.tabIcon,
|
||||
tooltipText: api.name,
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => {
|
||||
switchToTab(id);
|
||||
}
|
||||
})),
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
export default Widget.Box({
|
||||
properties: [
|
||||
['nextTab', () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1))],
|
||||
['prevTab', () => switchToTab(Math.max(0, currentApiId-1))],
|
||||
],
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
homogeneous: false,
|
||||
@@ -84,5 +116,5 @@ export default Widget.Box({
|
||||
apiContentStack,
|
||||
apiCommandStack,
|
||||
textboxArea,
|
||||
]
|
||||
],
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ export default () => PopupWindow({
|
||||
focusable: true,
|
||||
anchor: ['left', 'top', 'bottom'],
|
||||
name: 'sideleft',
|
||||
// exclusivity: 'exclusive',
|
||||
showClassName: 'sideleft-show',
|
||||
hideClassName: 'sideleft-hide',
|
||||
child: SidebarLeft(),
|
||||
|
||||
@@ -18,6 +18,9 @@ export const SidebarModule = ({
|
||||
className: 'txt-small txt',
|
||||
label: `${name}`,
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
Label({
|
||||
className: 'sidebar-module-btn-arrow',
|
||||
})
|
||||
|
||||
@@ -7,6 +7,5 @@ import { SidebarModule } from './module.js';
|
||||
export const QuickScripts = () => SidebarModule({
|
||||
name: 'Quick scripts',
|
||||
child: Box({
|
||||
|
||||
})
|
||||
})
|
||||
@@ -7,77 +7,137 @@ import { setupCursorHover } from "../../lib/cursorhover.js";
|
||||
import { NavigationIndicator } from "../../lib/navigationindicator.js";
|
||||
import toolBox from './toolbox.js';
|
||||
import apiWidgets from './apiwidgets.js';
|
||||
import { chatEntry } from './apiwidgets.js';
|
||||
import apiwidgets, { chatEntry } from './apiwidgets.js';
|
||||
|
||||
const SidebarTabButton = (stack, stackItem, navIndicator, navIndex, icon, label) => Widget.Button({
|
||||
const contents = [
|
||||
{
|
||||
name: 'apis',
|
||||
content: apiWidgets,
|
||||
materialIcon: 'api',
|
||||
friendlyName: 'APIs',
|
||||
},
|
||||
{
|
||||
name: 'tools',
|
||||
content: toolBox,
|
||||
materialIcon: 'home_repair_service',
|
||||
friendlyName: 'Tools',
|
||||
},
|
||||
]
|
||||
let currentTabId = 0;
|
||||
|
||||
const contentStack = Stack({
|
||||
vexpand: true,
|
||||
transition: 'slide_left_right',
|
||||
items: contents.map(item => [item.name, item.content]),
|
||||
})
|
||||
|
||||
function switchToTab(id) {
|
||||
const allTabs = navTabs.get_children();
|
||||
const tabButton = allTabs[id];
|
||||
allTabs[currentTabId].toggleClassName('sidebar-selector-tab-active', false);
|
||||
allTabs[id].toggleClassName('sidebar-selector-tab-active', true);
|
||||
contentStack.shown = contents[id].name;
|
||||
if (tabButton) {
|
||||
// Fancy highlighter line width
|
||||
const buttonWidth = tabButton.get_allocated_width();
|
||||
const highlightWidth = tabButton.get_children()[0].get_allocated_width();
|
||||
navIndicator.css = `
|
||||
font-size: ${id}px;
|
||||
padding: 0px ${(buttonWidth - highlightWidth) / 2}px;
|
||||
`;
|
||||
}
|
||||
currentTabId = id;
|
||||
}
|
||||
const SidebarTabButton = (navIndex) => Widget.Button({
|
||||
// hexpand: true,
|
||||
className: 'sidebar-selector-tab',
|
||||
onClicked: (self) => {
|
||||
stack.shown = stackItem;
|
||||
// Add active class to self and remove for others
|
||||
const allTabs = self.get_parent().get_children();
|
||||
for (let i = 0; i < allTabs.length; i++) {
|
||||
if (allTabs[i] != self) allTabs[i].toggleClassName('sidebar-selector-tab-active', false);
|
||||
else self.toggleClassName('sidebar-selector-tab-active', true);
|
||||
}
|
||||
// Fancy highlighter line width
|
||||
const buttonWidth = self.get_allocated_width();
|
||||
const highlightWidth = self.get_children()[0].get_allocated_width();
|
||||
navIndicator.css = `
|
||||
font-size: ${navIndex}px;
|
||||
padding: 0px ${(buttonWidth - highlightWidth) / 2}px;
|
||||
`;
|
||||
switchToTab(navIndex);
|
||||
},
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
MaterialIcon(icon, 'larger'),
|
||||
MaterialIcon(contents[navIndex].materialIcon, 'larger'),
|
||||
Label({
|
||||
className: 'txt txt-smallie',
|
||||
label: label,
|
||||
label: `${contents[navIndex].friendlyName}`,
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: (button) => Utils.timeout(1, () => {
|
||||
setupCursorHover(button);
|
||||
button.toggleClassName('sidebar-selector-tab-active', defaultTab === stackItem);
|
||||
button.toggleClassName('sidebar-selector-tab-active', currentTabId == navIndex);
|
||||
}),
|
||||
});
|
||||
|
||||
const defaultTab = 'apis';
|
||||
const contentStack = Stack({
|
||||
vexpand: true,
|
||||
transition: 'slide_left_right',
|
||||
items: [
|
||||
['apis', apiWidgets],
|
||||
['tools', toolBox],
|
||||
],
|
||||
})
|
||||
const navTabs = Box({
|
||||
homogeneous: true,
|
||||
children: contents.map((item, id) =>
|
||||
SidebarTabButton(id, item.materialIcon, item.friendlyName)
|
||||
),
|
||||
});
|
||||
|
||||
const navIndicator = NavigationIndicator(2, false, { // The line thing
|
||||
className: 'sidebar-selector-highlight',
|
||||
css: 'font-size: 0px; padding: 0rem 4.773rem;', // Shushhhh
|
||||
})
|
||||
css: 'font-size: 0px; padding: 0rem 4.160rem;', // Shushhhh
|
||||
});
|
||||
|
||||
const navBar = Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Box({
|
||||
homogeneous: true,
|
||||
children: [
|
||||
SidebarTabButton(contentStack, 'apis', navIndicator, 0, 'api', 'APIs'),
|
||||
SidebarTabButton(contentStack, 'tools', navIndicator, 1, 'home_repair_service', 'Tools'),
|
||||
]
|
||||
}),
|
||||
navTabs,
|
||||
navIndicator,
|
||||
]
|
||||
});
|
||||
|
||||
const pinButton = Button({
|
||||
properties: [
|
||||
['enabled', false],
|
||||
['toggle', (self) => {
|
||||
self._enabled = !self._enabled;
|
||||
self.toggleClassName('sidebar-pin-enabled', self._enabled);
|
||||
|
||||
const sideleftWindow = App.getWindow('sideleft');
|
||||
const barWindow = App.getWindow('bar');
|
||||
const cornerTopLeftWindow = App.getWindow('cornertl');
|
||||
const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1];
|
||||
|
||||
sideleftContent.toggleClassName('sidebar-pinned', self._enabled);
|
||||
|
||||
if (self._enabled) {
|
||||
sideleftWindow.layer = 'bottom';
|
||||
barWindow.layer = 'bottom';
|
||||
cornerTopLeftWindow.layer = 'bottom';
|
||||
sideleftWindow.exclusivity = 'exclusive';
|
||||
}
|
||||
else {
|
||||
sideleftWindow.layer = 'top';
|
||||
barWindow.layer = 'top';
|
||||
cornerTopLeftWindow.layer = 'top';
|
||||
sideleftWindow.exclusivity = 'normal';
|
||||
}
|
||||
}],
|
||||
],
|
||||
vpack: 'start',
|
||||
className: 'sidebar-pin',
|
||||
child: MaterialIcon('push_pin', 'larger'),
|
||||
tooltipText: 'Pin sidebar',
|
||||
onClicked: (self) => self._toggle(self),
|
||||
// QoL: Focus Pin button on open. Hit keybind -> space/enter = toggle pin state
|
||||
connections: [[App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideleft' && visible) {
|
||||
self.grab_focus();
|
||||
}
|
||||
}]]
|
||||
})
|
||||
|
||||
export default () => Box({
|
||||
// vertical: true,
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
EventBox({
|
||||
onPrimaryClick: () => App.closeWindow('sideleft'),
|
||||
@@ -87,23 +147,65 @@ export default () => Box({
|
||||
Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
className: 'sidebar-left',
|
||||
className: 'sidebar-left spacing-v-10',
|
||||
children: [
|
||||
navBar,
|
||||
Box({
|
||||
className: 'spacing-h-10',
|
||||
children: [
|
||||
navBar,
|
||||
pinButton,
|
||||
]
|
||||
}),
|
||||
contentStack,
|
||||
]
|
||||
],
|
||||
connections: [[App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideleft') {
|
||||
self.toggleClassName('sidebar-pinned', pinButton._enabled && visible);
|
||||
}
|
||||
}]]
|
||||
}),
|
||||
],
|
||||
connections: [
|
||||
['key-press-event', (widget, event) => { // Typing
|
||||
if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
|
||||
widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space) {
|
||||
if (contentStack.shown == 'apis') {
|
||||
['key-press-event', (widget, event) => { // Handle keybinds
|
||||
if (event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) {
|
||||
// Pin sidebar
|
||||
if (event.get_keyval()[1] == Gdk.KEY_p)
|
||||
pinButton._toggle(pinButton);
|
||||
// Switch sidebar tab
|
||||
else if (event.get_keyval()[1] === Gdk.KEY_Tab)
|
||||
switchToTab((currentTabId + 1) % contents.length);
|
||||
else if (event.get_keyval()[1] === Gdk.KEY_Page_Up)
|
||||
switchToTab(Math.max(currentTabId - 1), 0);
|
||||
else if (event.get_keyval()[1] === Gdk.KEY_Page_Down)
|
||||
switchToTab(Math.min(currentTabId + 1), contents.length);
|
||||
}
|
||||
if (contentStack.shown == 'apis') { // If api tab is focused
|
||||
// Automatically focus entry when typing
|
||||
if ((
|
||||
!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
|
||||
widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space)
|
||||
||
|
||||
((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] === Gdk.KEY_v)
|
||||
) {
|
||||
chatEntry.grab_focus();
|
||||
chatEntry.set_text(chatEntry.text + String.fromCharCode(event.get_keyval()[1]));
|
||||
chatEntry.set_position(-1);
|
||||
}
|
||||
// Switch API type
|
||||
else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] === Gdk.KEY_Page_Down) {
|
||||
const toSwitchTab = contentStack.get_visible_child();
|
||||
toSwitchTab._nextTab();
|
||||
}
|
||||
else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] === Gdk.KEY_Page_Up) {
|
||||
const toSwitchTab = contentStack.get_visible_child();
|
||||
toSwitchTab._prevTab();
|
||||
}
|
||||
}
|
||||
|
||||
}],
|
||||
],
|
||||
});
|
||||
|
||||
@@ -65,12 +65,14 @@ const CalendarWidget = () => {
|
||||
}
|
||||
});
|
||||
const addCalendarChildren = (box, calendarJson) => {
|
||||
const children = box.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
box.children = calendarJson.map((row, i) => Widget.Box({
|
||||
// homogeneous: true,
|
||||
className: 'spacing-h-5',
|
||||
children: row.map((day, i) =>
|
||||
CalendarDay(day.day, day.today)
|
||||
)
|
||||
children: row.map((day, i) => CalendarDay(day.day, day.today)),
|
||||
}))
|
||||
}
|
||||
function shiftCalendarXMonths(x) {
|
||||
|
||||
@@ -29,17 +29,15 @@ export const ToggleIconWifi = (props = {}) => Widget.Button({
|
||||
export const ToggleIconBluetooth = (props = {}) => Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Bluetooth | Right-click to configure',
|
||||
onClicked: () => { // Provided service doesn't work hmmm
|
||||
onClicked: () => {
|
||||
const status = Bluetooth?.enabled;
|
||||
if (status) {
|
||||
if (status)
|
||||
exec('rfkill block bluetooth');
|
||||
}
|
||||
else {
|
||||
else
|
||||
exec('rfkill unblock bluetooth');
|
||||
}
|
||||
},
|
||||
onSecondaryClickRelease: () => {
|
||||
execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center bluetooth', '&']);
|
||||
execAsync(['bash', '-c', 'blueberry &']);
|
||||
},
|
||||
child: BluetoothIndicator(),
|
||||
connections: [
|
||||
@@ -70,24 +68,28 @@ export const HyprToggleIcon = (icon, name, hyprlandConfigValue, props = {}) => W
|
||||
...props,
|
||||
})
|
||||
|
||||
export const ModuleNightLight = (props = {}) => Widget.Button({
|
||||
export const ModuleNightLight = (props = {}) => Widget.Button({ // TODO: Make this work
|
||||
properties: [
|
||||
['enabled', false],
|
||||
['yellowlight', undefined],
|
||||
],
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Night Light',
|
||||
onClicked: (button) => {
|
||||
const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
|
||||
if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
|
||||
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader ''`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', false);
|
||||
}
|
||||
else {
|
||||
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader ~/.config/hypr/shaders/extradark.frag`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', true);
|
||||
}
|
||||
onClicked: (self) => {
|
||||
self._enabled = !self._enabled;
|
||||
self.toggleClassName('sidebar-button-active', self._enabled);
|
||||
// if (self._enabled) Utils.execAsync(['bash', '-c', 'wlsunset & disown'])
|
||||
if (self._enabled) Utils.execAsync('wlsunset')
|
||||
else Utils.execAsync('pkill wlsunset');
|
||||
},
|
||||
child: MaterialIcon('nightlight', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self._enabled = !!exec('pidof wlsunset');
|
||||
self.toggleClassName('sidebar-button-active', self._enabled);
|
||||
},
|
||||
...props,
|
||||
})
|
||||
});
|
||||
|
||||
export const ModuleInvertColors = (props = {}) => Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
|
||||
@@ -41,18 +41,18 @@ import { ModuleCalendar } from "./calendar.js";
|
||||
const timeRow = Box({
|
||||
className: 'spacing-h-5 sidebar-group-invisible-morehorizpad',
|
||||
children: [
|
||||
Widget.Label({
|
||||
className: 'txt-title txt',
|
||||
connections: [[5000, label => {
|
||||
label.label = GLib.DateTime.new_now_local().format("%H:%M");
|
||||
}]],
|
||||
}),
|
||||
// Widget.Label({
|
||||
// className: 'txt-title txt',
|
||||
// connections: [[5000, label => {
|
||||
// label.label = GLib.DateTime.new_now_local().format("%H:%M");
|
||||
// }]],
|
||||
// }),
|
||||
Widget.Label({
|
||||
hpack: 'center',
|
||||
className: 'txt-small txt',
|
||||
connections: [[5000, label => {
|
||||
execAsync(['bash', '-c', `uptime -p | sed -e 's/up //;s/ hours,/h/;s/ minutes/m/'`]).then(upTimeString => {
|
||||
label.label = `• up: ${upTimeString}`;
|
||||
label.label = `System uptime: ${upTimeString}`;
|
||||
}).catch(print);
|
||||
}]],
|
||||
}),
|
||||
@@ -81,6 +81,7 @@ const togglesBox = Widget.Box({
|
||||
export default () => Box({
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
EventBox({
|
||||
onPrimaryClick: () => App.closeWindow('sideright'),
|
||||
|
||||
Reference in New Issue
Block a user