-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprefs.js
More file actions
359 lines (311 loc) · 16.4 KB
/
Copy pathprefs.js
File metadata and controls
359 lines (311 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
'use strict';
import Adw from 'gi://Adw';
import Gtk from 'gi://Gtk';
import Gdk from 'gi://Gdk';
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
export default class AnyWidgetPreferences extends ExtensionPreferences {
fillPreferencesWindow(window) {
this.settings = this.getSettings();
// CSS properties
const provider = new Gtk.CssProvider();
provider.load_from_string(`
.widget-list-row { margin: 6px; }
.delete-button { color: red; }
.code-editor { font-family: monospace; padding: 10px; }
`);
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
const page = new Adw.PreferencesPage();
const group = new Adw.PreferencesGroup({ title: _("Manage Widgets") });
page.add(group);
const addRow = new Adw.ActionRow({ title: _("Create New Widget") });
const addButton = new Gtk.Button({
label: _("Add"),
css_classes: ['suggested-action']
});
addButton.connect('clicked', () => this._openWidgetEditor(window, null));
addRow.add_suffix(addButton);
group.add(addRow);
this._widgetsListGroup = new Adw.PreferencesGroup({ title: _("Active Widgets") });
page.add(this._widgetsListGroup);
this._refreshWidgetList(window);
this.settings.connect('changed::widget-list', () => this._refreshWidgetList(window));
window.add(page);
}
_refreshWidgetList(window) {
if (this._currentRows) {
this._currentRows.forEach(row => this._widgetsListGroup.remove(row));
}
this._currentRows = [];
const widgets = this._getWidgets();
if (widgets.length === 0) {
const emptyRow = new Adw.ActionRow({ title: _("No active widgets") });
this._widgetsListGroup.add(emptyRow);
this._currentRows.push(emptyRow);
return;
}
widgets.forEach(widget => {
const row = new Adw.ActionRow({
title: widget.name,
subtitle: widget.loader_type.toUpperCase()
});
const editBtn = new Gtk.Button({
icon_name: 'document-edit-symbolic',
valign: Gtk.Align.CENTER,
tooltip_text: _("Edit Widget")
});
editBtn.connect('clicked', () => this._openWidgetEditor(window, widget));
row.add_suffix(editBtn);
const delBtn = new Gtk.Button({
icon_name: 'user-trash-symbolic',
valign: Gtk.Align.CENTER,
css_classes: ['destructive-action'],
tooltip_text: _("Delete Widget")
});
delBtn.connect('clicked', () => this._deleteWidget(widget.id, window));
row.add_suffix(delBtn);
this._widgetsListGroup.add(row);
this._currentRows.push(row);
});
}
_getWidgets() {
try {
return JSON.parse(this.settings.get_string('widget-list') || '[]');
} catch (e) {
return [];
}
}
_saveWidgets(widgets) {
this.settings.set_string('widget-list', JSON.stringify(widgets));
}
_deleteWidget(id, window) {
const widgets = this._getWidgets().filter(w => w.id !== id);
this._saveWidgets(widgets);
}
_openWidgetEditor(parentWindow, widgetData) {
const isNew = !widgetData;
const widget = widgetData || {
id: GLib.uuid_string_random(),
name: "New Widget",
loader_type: 'code',
loader_source: `// Example: Create a simple Label
const label = new St.Label({
text: "Your anyWidget",
style_class: "any-widget-label"
});
return label;`,
width: 200,
height: 100,
widget_position_x: 100,
widget_position_y: 100,
background_color: 'rgba(0,0,0,0.5)',
corner_radius: 12,
transparency: 100,
always_on_top: false,
enabled: true,
border_width: 0,
border_color: 'rgba(255,255,255,0.2)',
shadow_blur: 0,
shadow_color: 'rgba(0,0,0,1)',
background_blur: false,
blur_radius: 30,
click_through: false,
hover_scale: false
};
const dialog = new Adw.Window({
title: isNew ? _("New Widget") : _("Edit Widget"),
transient_for: parentWindow,
modal: true,
default_width: 700,
default_height: 800
});
const content = new Adw.ToolbarView();
dialog.set_content(content);
const header = new Adw.HeaderBar();
content.add_top_bar(header);
const cancelBtn = new Gtk.Button({ label: _("Cancel") });
cancelBtn.connect('clicked', () => dialog.close());
header.pack_start(cancelBtn);
const saveBtn = new Gtk.Button({ label: _("Save"), css_classes: ['suggested-action'] });
saveBtn.connect('clicked', () => {
if (widget.loader_type === 'code' && codeBuffer) {
const start = codeBuffer.get_start_iter();
const end = codeBuffer.get_end_iter();
widget.loader_source = codeBuffer.get_text(start, end, false);
}
this._saveWidgetData(widget, dialog);
});
header.pack_end(saveBtn);
const stack = new Adw.ViewStack();
const switcherBar = new Adw.ViewSwitcherBar({ stack: stack });
content.add_bottom_bar(switcherBar);
const switcherTitle = new Adw.ViewSwitcherTitle({ stack: stack, title: isNew ? _("New Widget") : _("Edit Widget") });
header.set_title_widget(switcherTitle);
// === Code Tab ===
const codePage = new Adw.PreferencesPage();
const codeGroup = new Adw.PreferencesGroup();
codePage.add(codeGroup);
const nameRow = new Adw.EntryRow({ title: _("Widget Name"), text: widget.name });
nameRow.connect('notify::text', () => widget.name = nameRow.get_text());
codeGroup.add(nameRow);
const typeRow = new Adw.ComboRow({
title: _("Source Type"),
model: new Gtk.StringList({ strings: ['JavaScript Code', 'File Path'] })
});
const types = ['code', 'file'];
typeRow.set_selected(types.indexOf(widget.loader_type) !== -1 ? types.indexOf(widget.loader_type) : 0);
codeGroup.add(typeRow);
const inputGroup = new Adw.PreferencesGroup({ title: _("Source") });
codePage.add(inputGroup);
let codeBuffer;
const updateInputState = () => {
const selectedType = types[typeRow.get_selected()];
widget.loader_type = selectedType;
codeBox.set_visible(selectedType === 'code');
fileRow.set_visible(selectedType === 'file');
};
const autoSave = () => this._saveWidgetData(widget, null);
const codeBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin_top: 10, margin_bottom: 10, spacing: 6 });
const codeLabel = new Gtk.Label({ label: _("JavaScript Code (Must return a Clutter Actor)"), xalign: 0, css_classes: ['heading'] });
const helpLabel = new Gtk.Label({ label: _("Available globals: St, Clutter, GObject, Gio, GLib, Main. \nExample: return new St.Label({ text: 'Hi' });"), xalign: 0, css_classes: ['dim-label'], wrap: true });
codeBox.append(codeLabel);
codeBox.append(helpLabel);
const scrolled = new Gtk.ScrolledWindow({ min_content_height: 300, has_frame: true, hexpand: true, vexpand: true });
const textView = new Gtk.TextView({ monospace: true, top_margin: 8, bottom_margin: 8, left_margin: 8, right_margin: 8 });
codeBuffer = textView.get_buffer();
if (widget.loader_type === 'code') codeBuffer.set_text(widget.loader_source || "", -1);
scrolled.set_child(textView);
codeBox.append(scrolled);
inputGroup.add(codeBox);
const fileRow = new Adw.ActionRow({ title: _("Select JavaScript File") });
const fileBtn = new Gtk.Button({ label: _("Browse..."), valign: Gtk.Align.CENTER });
const fileLabel = new Gtk.Label({ label: widget.loader_type === 'file' ? (widget.loader_source || _("No file selected")) : _("No file selected"), ellipsize: 3 });
fileRow.add_suffix(fileLabel);
fileRow.add_suffix(fileBtn);
fileBtn.connect('clicked', () => {
const chooser = new Gtk.FileChooserDialog({ title: _("Select JS File"), transient_for: dialog, action: Gtk.FileChooserAction.OPEN });
chooser.add_button(_("Cancel"), Gtk.ResponseType.CANCEL);
chooser.add_button(_("Select"), Gtk.ResponseType.ACCEPT);
const filter = new Gtk.FileFilter();
filter.set_name("JavaScript Files");
filter.add_pattern("*.js");
chooser.add_filter(filter);
// Default to widgets folder
try {
const widgetsDir = GLib.build_filenamev([this.path, 'widgets']);
const widgetsFile = Gio.File.new_for_path(widgetsDir);
if (widgetsFile.query_exists(null)) {
chooser.set_current_folder(widgetsFile);
}
} catch (e) { /* ignore */ }
chooser.connect('response', (d, response) => {
if (response === Gtk.ResponseType.ACCEPT) {
const file = chooser.get_file();
const path = file.get_path();
if (path) { widget.loader_source = path; fileLabel.set_label(path); autoSave(); }
}
chooser.destroy();
});
chooser.present();
});
inputGroup.add(fileRow);
typeRow.connect('notify::selected', () => { updateInputState(); });
updateInputState();
stack.add_titled(codePage, "code", _("Source"));
// === Appearance Tab ===
const uiPage = new Adw.PreferencesPage();
const uiGroup = new Adw.PreferencesGroup({ title: _("Geometry & Transparency") });
uiPage.add(uiGroup);
const createSpin = (title, lower, upper, step, val, key) => {
const row = new Adw.SpinRow({ title: title, adjustment: new Gtk.Adjustment({ lower, upper, step_increment: step, value: val }) });
row.connect('notify::value', () => { widget[key] = row.get_value(); autoSave(); });
uiGroup.add(row);
};
createSpin(_("X Position"), 0, 10000, 1, widget.widget_position_x, 'widget_position_x');
createSpin(_("Y Position"), 0, 10000, 1, widget.widget_position_y, 'widget_position_y');
createSpin(_("Width"), 50, 3000, 10, widget.width, 'width');
createSpin(_("Height"), 50, 3000, 10, widget.height, 'height');
const opacityRow = new Adw.ActionRow({ title: _("Global Opacity %") });
const opacityScale = new Gtk.Scale({ orientation: Gtk.Orientation.HORIZONTAL, adjustment: new Gtk.Adjustment({ lower: 0, upper: 100, step_increment: 1, value: widget.transparency !== undefined ? widget.transparency : 100 }), draw_value: true, hexpand: true });
opacityScale.connect('value-changed', () => { widget.transparency = opacityScale.get_value(); autoSave(); });
opacityRow.add_suffix(opacityScale);
uiGroup.add(opacityRow);
const styleGroup = new Adw.PreferencesGroup({ title: _("Style") });
uiPage.add(styleGroup);
// Background Color
const colorRow = new Adw.ActionRow({ title: _("Background Color") });
const colorBtn = new Gtk.ColorButton();
const rgba = new Gdk.RGBA();
rgba.parse(widget.background_color || 'rgba(0,0,0,0.5)');
colorBtn.set_rgba(rgba);
colorBtn.connect('color-set', () => { const c = colorBtn.get_rgba(); widget.background_color = `rgba(${Math.round(c.red * 255)},${Math.round(c.green * 255)},${Math.round(c.blue * 255)},${c.alpha})`; autoSave(); });
colorRow.add_suffix(colorBtn);
styleGroup.add(colorRow);
// Corner Radius
const radiusRow = new Adw.SpinRow({ title: _("Corner Radius (px)"), adjustment: new Gtk.Adjustment({ lower: 0, upper: 200, step_increment: 1, value: widget.corner_radius || 0 }) });
radiusRow.connect('notify::value', () => { widget.corner_radius = radiusRow.get_value(); autoSave(); });
styleGroup.add(radiusRow);
// Border Width & Color
const borderWRow = new Adw.SpinRow({ title: _("Border Width (px)"), adjustment: new Gtk.Adjustment({ lower: 0, upper: 20, step_increment: 1, value: widget.border_width || 0 }) });
borderWRow.connect('notify::value', () => { widget.border_width = borderWRow.get_value(); autoSave(); });
styleGroup.add(borderWRow);
const borderColRow = new Adw.ActionRow({ title: _("Border Color") });
const borderColBtn = new Gtk.ColorButton();
const bRgba = new Gdk.RGBA();
bRgba.parse(widget.border_color || 'rgba(255,255,255,0.2)');
borderColBtn.set_rgba(bRgba);
borderColBtn.connect('color-set', () => { const c = borderColBtn.get_rgba(); widget.border_color = `rgba(${Math.round(c.red * 255)},${Math.round(c.green * 255)},${Math.round(c.blue * 255)},${c.alpha})`; autoSave(); });
borderColRow.add_suffix(borderColBtn);
styleGroup.add(borderColRow);
// Drop Shadow
const shadowBlurRow = new Adw.SpinRow({ title: _("Shadow Blur (px)"), adjustment: new Gtk.Adjustment({ lower: 0, upper: 100, step_increment: 1, value: widget.shadow_blur || 0 }) });
shadowBlurRow.connect('notify::value', () => { widget.shadow_blur = shadowBlurRow.get_value(); autoSave(); });
styleGroup.add(shadowBlurRow);
const shadowColRow = new Adw.ActionRow({ title: _("Shadow Color") });
const shadowColBtn = new Gtk.ColorButton();
const sRgba = new Gdk.RGBA();
sRgba.parse(widget.shadow_color || 'rgba(0,0,0,1)');
shadowColBtn.set_rgba(sRgba);
shadowColBtn.connect('color-set', () => { const c = shadowColBtn.get_rgba(); widget.shadow_color = `rgba(${Math.round(c.red * 255)},${Math.round(c.green * 255)},${Math.round(c.blue * 255)},${c.alpha})`; autoSave(); });
shadowColRow.add_suffix(shadowColBtn);
styleGroup.add(shadowColRow);
// Backdrop Blur (Glass Effect)
const blurRow = new Adw.SwitchRow({ title: _("Background Blur"), subtitle: _("Enable glassmorphism effect") });
blurRow.set_active(widget.background_blur === true);
blurRow.connect('notify::active', () => { widget.background_blur = blurRow.get_active(); autoSave(); });
styleGroup.add(blurRow);
stack.add_titled(uiPage, "style", _("Appearance"));
// === Interaction Tab ===
const interactPage = new Adw.PreferencesPage();
const interactGroup = new Adw.PreferencesGroup({ title: _("Behavior") });
interactPage.add(interactGroup);
const topRow = new Adw.SwitchRow({ title: _("Always on Top"), subtitle: _("Keep overlapping other windows") });
topRow.set_active(widget.always_on_top === true);
topRow.connect('notify::active', () => { widget.always_on_top = topRow.get_active(); autoSave(); });
interactGroup.add(topRow);
const clickRow = new Adw.SwitchRow({ title: _("Click-Through Mode"), subtitle: _("Ignore mouse events (cannot resize/drag when enabled)") });
clickRow.set_active(widget.click_through === true);
clickRow.connect('notify::active', () => { widget.click_through = clickRow.get_active(); autoSave(); });
interactGroup.add(clickRow);
const hoverRow = new Adw.SwitchRow({ title: _("Hover Scale Effect"), subtitle: _("Slightly zoom when hovered") });
hoverRow.set_active(widget.hover_scale === true);
hoverRow.connect('notify::active', () => { widget.hover_scale = hoverRow.get_active(); autoSave(); });
interactGroup.add(hoverRow);
stack.add_titled(interactPage, "behavior", _("Behavior"));
content.set_content(stack);
dialog.present();
}
_saveWidgetData(widget, dialog) {
let widgets = this._getWidgets();
const index = widgets.findIndex(w => w.id === widget.id);
if (index !== -1) {
widgets[index] = widget;
} else {
widgets.push(widget);
}
this._saveWidgets(widgets);
if (dialog) dialog.close();
}
}