[gnome] Updating extensions, back to Forge
This commit is contained in:
@ -0,0 +1,221 @@
|
||||
// Gnome imports
|
||||
import Adw from "gi://Adw";
|
||||
import GObject from "gi://GObject";
|
||||
import Gdk from "gi://Gdk";
|
||||
|
||||
// Extension imports
|
||||
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
||||
|
||||
// Shared state
|
||||
import { ConfigManager } from "../shared/settings.js";
|
||||
import { PrefsThemeManager } from "./prefs-theme-manager.js";
|
||||
|
||||
// Prefs UI
|
||||
import { ColorRow, PreferencesPage, ResetButton, SpinButtonRow, SwitchRow } from "./widgets.js";
|
||||
import { Logger } from "../shared/logger.js";
|
||||
|
||||
export class AppearancePage extends PreferencesPage {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
*/
|
||||
static getCssSelectorAsMessage(selector) {
|
||||
switch (selector) {
|
||||
// TODO: make separate color selection for preview hint
|
||||
case ".window-tiled-border":
|
||||
return _("Tiled window");
|
||||
case ".window-tabbed-border":
|
||||
return _("Tabbed window");
|
||||
case ".window-stacked-border":
|
||||
return _("Stacked window");
|
||||
case ".window-floated-border":
|
||||
return _("Floating window");
|
||||
case ".window-split-border":
|
||||
return _("Split direction hint");
|
||||
}
|
||||
}
|
||||
|
||||
constructor({ settings, dir }) {
|
||||
super({ title: _("Appearance"), icon_name: "brush-symbolic" });
|
||||
this.settings = settings;
|
||||
let configMgr = new ConfigManager({ dir });
|
||||
this.themeMgr = new PrefsThemeManager({ configMgr: configMgr, settings: settings });
|
||||
this.add_group({
|
||||
title: _("Gaps"),
|
||||
description: _("Change the gap size between windows"),
|
||||
children: [
|
||||
new SpinButtonRow({
|
||||
title: _("Gap size"),
|
||||
range: [0, 32, 1],
|
||||
settings,
|
||||
bind: "window-gap-size",
|
||||
}),
|
||||
new SpinButtonRow({
|
||||
title: _("Gap size multiplier"),
|
||||
range: [0, 32, 1],
|
||||
settings,
|
||||
bind: "window-gap-size-increment",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Disable gaps for single window"),
|
||||
subtitle: _("Disables window gaps when only a single window is present"),
|
||||
settings,
|
||||
bind: "window-gap-hidden-on-single",
|
||||
}),
|
||||
],
|
||||
});
|
||||
this.add_group({
|
||||
title: _("Style"),
|
||||
description: _("Change how the shell looks"),
|
||||
children: [
|
||||
new SwitchRow({
|
||||
title: _("Preview hint"),
|
||||
subtitle: _("Shows where the window will be tiled when you let go of it"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "preview-hint-enabled",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Border around focused window"),
|
||||
subtitle: _("Display a colored border around the focused window"),
|
||||
settings,
|
||||
bind: "focus-border-toggle",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Window split hint border"),
|
||||
subtitle: _("Show split direction border on focused window"),
|
||||
settings,
|
||||
bind: "split-border-toggle",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Forge in quick settings"),
|
||||
subtitle: _("Toggles the Forge tile in quick settings"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "quick-settings-enabled",
|
||||
}),
|
||||
],
|
||||
});
|
||||
this.add_group({
|
||||
title: _("Color"),
|
||||
description: _("Changes the focused window's border and preview hint colors"),
|
||||
children: [
|
||||
"window-tiled-border",
|
||||
"window-tabbed-border",
|
||||
"window-stacked-border",
|
||||
"window-floated-border",
|
||||
"window-split-border",
|
||||
].map((x) => this._createColorOptionWidget(x)),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} prefix
|
||||
*/
|
||||
_createColorOptionWidget(prefix) {
|
||||
const selector = `.${prefix}`;
|
||||
const theme = this.themeMgr;
|
||||
const title = AppearancePage.getCssSelectorAsMessage(selector);
|
||||
const colorScheme = theme.getColorSchemeBySelector(selector);
|
||||
const row = new Adw.ExpanderRow({ title });
|
||||
|
||||
const borderSizeRow = new SpinButtonRow({
|
||||
title: _("Border size"),
|
||||
range: [1, 6, 1],
|
||||
// subtitle: 'Properties of the focus hint',
|
||||
max_width_chars: 1,
|
||||
max_length: 1,
|
||||
width_chars: 2,
|
||||
xalign: 1,
|
||||
init: theme.removePx(theme.getCssProperty(selector, "border-width").value),
|
||||
onChange: (value) => {
|
||||
const px = theme.addPx(value);
|
||||
Logger.debug(`Setting border width for selector: ${selector} ${px}`);
|
||||
theme.setCssProperty(selector, "border-width", px);
|
||||
},
|
||||
});
|
||||
|
||||
borderSizeRow.add_suffix(
|
||||
new ResetButton({
|
||||
onReset: () => {
|
||||
const borderDefault = theme.defaultPalette[colorScheme]["border-width"];
|
||||
theme.setCssProperty(selector, "border-width", theme.addPx(borderDefault));
|
||||
borderSizeRow.activatable_widget.value = borderDefault;
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const updateCssColors = (rgbaString) => {
|
||||
const rgba = new Gdk.RGBA();
|
||||
|
||||
if (rgba.parse(rgbaString)) {
|
||||
Logger.debug(`Setting color for selector: ${selector} ${rgbaString}`);
|
||||
const previewBorderRgba = rgba.copy();
|
||||
const previewBackgroundRgba = rgba.copy();
|
||||
const overviewBackgroundRgba = rgba.copy();
|
||||
|
||||
previewBorderRgba.alpha = 0.3;
|
||||
previewBackgroundRgba.alpha = 0.2;
|
||||
overviewBackgroundRgba.alpha = 0.5;
|
||||
|
||||
// The primary color updates the focus hint:
|
||||
theme.setCssProperty(selector, "border-color", rgba.to_string());
|
||||
|
||||
// Only apply below on the tabbed scheme
|
||||
if (colorScheme === "tabbed") {
|
||||
const tabBorderRgba = rgba.copy();
|
||||
const tabActiveBackgroundRgba = rgba.copy();
|
||||
tabBorderRgba.alpha = 0.6;
|
||||
theme.setCssProperty(
|
||||
`.window-${colorScheme}-tab`,
|
||||
"border-color",
|
||||
tabBorderRgba.to_string()
|
||||
);
|
||||
theme.setCssProperty(
|
||||
`.window-${colorScheme}-tab-active`,
|
||||
"background-color",
|
||||
tabActiveBackgroundRgba.to_string()
|
||||
);
|
||||
}
|
||||
// And then finally the preview when doing drag/drop tiling:
|
||||
theme.setCssProperty(
|
||||
`.window-tilepreview-${colorScheme}`,
|
||||
"border-color",
|
||||
previewBorderRgba.to_string()
|
||||
);
|
||||
theme.setCssProperty(
|
||||
`.window-tilepreview-${colorScheme}`,
|
||||
"background-color",
|
||||
previewBackgroundRgba.to_string()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const borderColorRow = new ColorRow({
|
||||
title: _("Border color"),
|
||||
init: theme.getCssProperty(selector, "border-color").value,
|
||||
onChange: updateCssColors,
|
||||
});
|
||||
|
||||
borderColorRow.add_suffix(
|
||||
new ResetButton({
|
||||
onReset: () => {
|
||||
const selectorColor = theme.defaultPalette[colorScheme].color;
|
||||
updateCssColors(selectorColor);
|
||||
const rgba = new Gdk.RGBA();
|
||||
if (rgba.parse(selectorColor)) {
|
||||
borderColorRow.colorButton.set_rgba(rgba);
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
row.add_row(borderColorRow);
|
||||
row.add_row(borderSizeRow);
|
||||
|
||||
return row;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
// Gtk imports
|
||||
import Gtk from "gi://Gtk";
|
||||
import GObject from "gi://GObject";
|
||||
|
||||
// Gnome imports
|
||||
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
||||
|
||||
// Extension imports
|
||||
import { PreferencesPage, RemoveItemRow, ResetButton } from "./widgets.js";
|
||||
import { ConfigManager } from "../shared/settings.js";
|
||||
|
||||
export class FloatingPage extends PreferencesPage {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ settings, dir }) {
|
||||
super({ title: _("Windows"), icon_name: "window-symbolic" });
|
||||
|
||||
this.settings = settings;
|
||||
this.configMgr = new ConfigManager({ dir });
|
||||
|
||||
let overrides = this.configMgr.windowProps.overrides;
|
||||
this.rows = this.loadItemsFromConfig(overrides);
|
||||
|
||||
this.floatingWindowGroup = this.add_group({
|
||||
title: _("Floating Windows"),
|
||||
description: _("Windows that will not be tiled"),
|
||||
header_suffix: new ResetButton({ onReset: () => this.onResetHandler() }),
|
||||
children: this.rows,
|
||||
});
|
||||
}
|
||||
|
||||
loadItemsFromConfig(overrides) {
|
||||
let children = [];
|
||||
for (let override of overrides) {
|
||||
if (override.mode === "float") {
|
||||
let itemrow = new RemoveItemRow({
|
||||
title: override.wmTitle ?? override.wmClass,
|
||||
subtitle: override.wmClass,
|
||||
onRemove: (item, parent) => this.onRemoveHandler(item, parent),
|
||||
});
|
||||
children.push(itemrow);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
onRemoveHandler(item, parent) {
|
||||
this.floatingWindowGroup.remove(parent);
|
||||
this.rows = this.rows.filter((row) => row != parent);
|
||||
const existing = this.configMgr.windowProps.overrides;
|
||||
const modified = existing.filter((row) => item != row.wmClass);
|
||||
this.saveOverrides(modified);
|
||||
}
|
||||
|
||||
saveOverrides(modified) {
|
||||
if (modified) {
|
||||
this.configMgr.windowProps = {
|
||||
overrides: modified,
|
||||
};
|
||||
// Signal the main extension to reload floating overrides
|
||||
const changed = Math.floor(Date.now() / 1000);
|
||||
this.settings.set_uint("window-overrides-reload-trigger", changed);
|
||||
}
|
||||
}
|
||||
|
||||
onResetHandler() {
|
||||
const defaultWindowProps = this.configMgr.loadDefaultWindowConfigContents();
|
||||
const original = defaultWindowProps.overrides;
|
||||
this.saveOverrides(original);
|
||||
|
||||
for (const child of this.rows) {
|
||||
this.floatingWindowGroup.remove(child);
|
||||
}
|
||||
|
||||
this.rows = this.loadItemsFromConfig(original);
|
||||
for (const item of this.rows) {
|
||||
this.floatingWindowGroup.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
// Gnome imports
|
||||
import Adw from "gi://Adw";
|
||||
import GObject from "gi://GObject";
|
||||
import Gtk from "gi://Gtk";
|
||||
|
||||
// Extension Imports
|
||||
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
||||
|
||||
// Prefs UI
|
||||
import { EntryRow, PreferencesPage, RadioRow } from "./widgets.js";
|
||||
import { Logger } from "../shared/logger.js";
|
||||
|
||||
export class KeyboardPage extends PreferencesPage {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ kbdSettings }) {
|
||||
super({ title: _("Keyboard"), icon_name: "input-keyboard-symbolic" });
|
||||
|
||||
this.add_group({
|
||||
title: _("Drag-and-drop modifier key"),
|
||||
description: _(
|
||||
"Change the modifier key for tiling windows via drag-and-drop. Select 'None' to always tile"
|
||||
),
|
||||
children: [
|
||||
new RadioRow({
|
||||
title: _("Modifier key"),
|
||||
settings: kbdSettings,
|
||||
bind: "mod-mask-mouse-tile",
|
||||
options: {
|
||||
Super: _("Super"),
|
||||
Ctrl: _("Ctrl"),
|
||||
Alt: _("Alt"),
|
||||
None: _("None"),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
this.add_group({
|
||||
title: _("Shortcuts"),
|
||||
description: _(
|
||||
'Change the tiling shortcuts. To clear a shortcut clear the input field. To apply a shortcut press enter. <a href="https://github.com/forge-ext/forge/wiki/Keyboard-Shortcuts">Syntax examples</a>'
|
||||
),
|
||||
children: Object.entries({
|
||||
window: "Tiling shortcuts",
|
||||
con: "Container shortcuts",
|
||||
workspace: "Workspace shortcuts",
|
||||
focus: "Appearance shortcuts",
|
||||
prefs: "Other shortcuts",
|
||||
}).map(([prefix, gettextKey]) =>
|
||||
KeyboardPage.makeKeygroupExpander(prefix, gettextKey, kbdSettings)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
static makeKeygroupExpander(prefix, gettextKey, settings) {
|
||||
const expander = new Adw.ExpanderRow({ title: _(gettextKey) });
|
||||
KeyboardPage.createKeyList(settings, prefix).forEach((key) =>
|
||||
expander.add_row(
|
||||
new EntryRow({
|
||||
title: key,
|
||||
settings,
|
||||
bind: key,
|
||||
map: {
|
||||
from(settings, bind) {
|
||||
return settings.get_strv(bind).join(",");
|
||||
},
|
||||
to(settings, bind, value) {
|
||||
if (!!value) {
|
||||
const mappings = value.split(",").map((x) => {
|
||||
const [, key, mods] = Gtk.accelerator_parse(x);
|
||||
return Gtk.accelerator_valid(key, mods) && Gtk.accelerator_name(key, mods);
|
||||
});
|
||||
if (mappings.every((x) => !!x)) {
|
||||
Logger.info("setting", bind, "to", mappings);
|
||||
settings.set_strv(bind, mappings);
|
||||
}
|
||||
} else {
|
||||
// If value deleted, unset the mapping
|
||||
settings.set_strv(bind, []);
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
return expander;
|
||||
}
|
||||
|
||||
static createKeyList(settings, categoryName) {
|
||||
return settings
|
||||
.list_keys()
|
||||
.filter((keyName) => !!keyName && !!categoryName && keyName.startsWith(categoryName))
|
||||
.sort((a, b) => {
|
||||
const aUp = a.toUpperCase();
|
||||
const bUp = b.toUpperCase();
|
||||
if (aUp < bUp) return -1;
|
||||
if (aUp > bUp) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export const developers = Object.entries([
|
||||
].reduce((acc, x) => ({ ...acc, [x.email]: acc[x.email] ?? x.name }), {})).map(([email, name]) => name + ' <' + email + '>')
|
||||
@ -0,0 +1,13 @@
|
||||
import GObject from "gi://GObject";
|
||||
|
||||
import { ThemeManagerBase } from "../shared/theme.js";
|
||||
|
||||
export class PrefsThemeManager extends ThemeManagerBase {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
reloadStylesheet() {
|
||||
this.settings.set_string("css-updated", Date.now().toString());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
// Gnome imports
|
||||
import Adw from "gi://Adw";
|
||||
import Gtk from "gi://Gtk";
|
||||
import GObject from "gi://GObject";
|
||||
|
||||
// Shared state
|
||||
import { Logger } from "../shared/logger.js";
|
||||
import { production } from "../shared/settings.js";
|
||||
|
||||
// Prefs UI
|
||||
import { DropDownRow, SwitchRow, PreferencesPage, EntryRow } from "./widgets.js";
|
||||
|
||||
// Extension imports
|
||||
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
||||
import { PACKAGE_VERSION } from "resource:///org/gnome/Shell/Extensions/js/misc/config.js";
|
||||
|
||||
import { developers } from "./metadata.js";
|
||||
|
||||
function showAboutWindow(parent, { version, description: comments }) {
|
||||
version = version ?? "development";
|
||||
const abt = new Adw.AboutWindow({
|
||||
...(parent && { transient_for: parent }),
|
||||
// TODO: fetch these from github at build time
|
||||
application_name: _("Forge"),
|
||||
application_icon: "forge-logo-symbolic",
|
||||
version: `${PACKAGE_VERSION}-${version.toString()}`,
|
||||
copyright: `© 2021-${new Date().getFullYear()} jmmaranan`,
|
||||
issue_url: "https://github.com/forge-ext/forge/issues/new",
|
||||
license_type: Gtk.License.GPL_3_0,
|
||||
website: "https://github.com/forge-ext/forge",
|
||||
developers,
|
||||
comments,
|
||||
designers: [],
|
||||
translator_credits: _("translator-credits"),
|
||||
});
|
||||
abt.present();
|
||||
}
|
||||
|
||||
function makeAboutButton(parent, metadata) {
|
||||
const button = new Gtk.Button({
|
||||
icon_name: "help-about-symbolic",
|
||||
tooltip_text: _("About"),
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
button.connect("clicked", () => showAboutWindow(parent, metadata));
|
||||
return button;
|
||||
}
|
||||
|
||||
export class SettingsPage extends PreferencesPage {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ settings, window, metadata }) {
|
||||
super({ title: _("Tiling"), icon_name: "view-grid-symbolic" });
|
||||
this.add_group({
|
||||
title: _("Behavior"),
|
||||
description: _("Change how the tiling behaves"),
|
||||
header_suffix: makeAboutButton(window, metadata),
|
||||
children: [
|
||||
new SwitchRow({
|
||||
title: _("Focus on Hover"),
|
||||
subtitle: _("Window focus follows the pointer"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "focus-on-hover-enabled",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Move pointer with focused window"),
|
||||
subtitle: _("Moves the pointer when focusing or swapping via keyboard"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "move-pointer-focus-enabled",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Quarter tiling"),
|
||||
subtitle: _("Places new windows in a clock-wise fashion"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "auto-split-enabled",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Stacked tiling"),
|
||||
subtitle: _("Stacks windows on top of each other while still tiling them"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "stacked-tiling-mode-enabled",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Tabbed tiling"),
|
||||
subtitle: _("Groups windows as tabs"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "tabbed-tiling-mode-enabled",
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Auto exit tabbed tiling"),
|
||||
subtitle: _("Exit tabbed tiling mode when only a single tab remains"),
|
||||
settings,
|
||||
bind: "auto-exit-tabbed",
|
||||
bind: "move-pointer-focus-enabled",
|
||||
}),
|
||||
new DropDownRow({
|
||||
title: _("Drag-and-drop behavior"),
|
||||
subtitle: _("What to do when dragging one window on top of another"),
|
||||
settings,
|
||||
type: "s",
|
||||
bind: "dnd-center-layout",
|
||||
items: [
|
||||
{ id: "swap", name: _("Swap") },
|
||||
{ id: "tabbed", name: _("Tabbed") },
|
||||
{ id: "stacked", name: _("Stacked") },
|
||||
],
|
||||
}),
|
||||
new SwitchRow({
|
||||
title: _("Always on Top mode for floating windows"),
|
||||
subtitle: _("Makes floating windows appear above tiled windows"),
|
||||
experimental: true,
|
||||
settings,
|
||||
bind: "float-always-on-top-enabled",
|
||||
}),
|
||||
],
|
||||
});
|
||||
this.add_group({
|
||||
title: _("Non-tiling workspaces"),
|
||||
description: _("Disables tiling on specified workspaces. Starts from 0, separated by commas"),
|
||||
children: [
|
||||
new EntryRow({
|
||||
title: _("Example: 0,1,2"),
|
||||
settings,
|
||||
bind: "workspace-skip-tile",
|
||||
}),
|
||||
],
|
||||
});
|
||||
if (!production) {
|
||||
this.add_group({
|
||||
title: _("Logger"),
|
||||
children: [
|
||||
new DropDownRow({
|
||||
title: _("Log level"),
|
||||
settings,
|
||||
bind: "log-level",
|
||||
items: Object.entries(Logger.LOG_LEVELS).map(([name, id]) => ({ id, name })),
|
||||
type: "u",
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,356 @@
|
||||
/** @license (c) aylur. GPL v3 */
|
||||
|
||||
import Adw from "gi://Adw";
|
||||
import Gio from "gi://Gio";
|
||||
import Gdk from "gi://Gdk";
|
||||
import Gtk from "gi://Gtk";
|
||||
import GObject from "gi://GObject";
|
||||
|
||||
// GNOME imports
|
||||
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
||||
|
||||
// Shared state
|
||||
import { Logger } from "../shared/logger.js";
|
||||
|
||||
export class PreferencesPage extends Adw.PreferencesPage {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
add_group({ title, description = "", children, header_suffix = "" }) {
|
||||
const group = new Adw.PreferencesGroup({ title, description });
|
||||
for (const child of children) group.add(child);
|
||||
if (header_suffix) group.set_header_suffix(header_suffix);
|
||||
this.add(group);
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
export class SwitchRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ title, settings, bind, subtitle = "", experimental = false }) {
|
||||
super({ title, subtitle });
|
||||
const gswitch = new Gtk.Switch({
|
||||
active: settings.get_boolean(bind),
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
settings.bind(bind, gswitch, "active", Gio.SettingsBindFlags.DEFAULT);
|
||||
if (experimental) {
|
||||
const icon = new Gtk.Image({ icon_name: "bug-symbolic" });
|
||||
icon.set_tooltip_markup(
|
||||
_("<b>CAUTION</b>: Enabling this setting can lead to bugs or cause the shell to crash")
|
||||
);
|
||||
this.add_suffix(icon);
|
||||
}
|
||||
this.add_suffix(gswitch);
|
||||
this.activatable_widget = gswitch;
|
||||
}
|
||||
}
|
||||
|
||||
export class ColorRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ title, init, onChange, subtitle = "" }) {
|
||||
super({ title, subtitle });
|
||||
let rgba = new Gdk.RGBA();
|
||||
rgba.parse(init);
|
||||
this.colorButton = new Gtk.ColorButton({ rgba, use_alpha: true, valign: Gtk.Align.CENTER });
|
||||
this.colorButton.connect("color-set", () => {
|
||||
onChange(this.colorButton.get_rgba().to_string());
|
||||
});
|
||||
this.add_suffix(this.colorButton);
|
||||
this.activatable_widget = this.colorButton;
|
||||
}
|
||||
}
|
||||
|
||||
export class SpinButtonRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({
|
||||
title,
|
||||
range: [low, high, step],
|
||||
subtitle = "",
|
||||
init = undefined,
|
||||
onChange = undefined,
|
||||
max_width_chars = undefined,
|
||||
max_length = undefined,
|
||||
width_chars = undefined,
|
||||
xalign = undefined,
|
||||
settings = undefined,
|
||||
bind = undefined,
|
||||
}) {
|
||||
super({ title, subtitle });
|
||||
const gspin = Gtk.SpinButton.new_with_range(low, high, step);
|
||||
gspin.xalign = 1;
|
||||
if (bind && settings) {
|
||||
settings.bind(bind, gspin, "value", Gio.SettingsBindFlags.DEFAULT);
|
||||
} else if (init) {
|
||||
gspin.value = init;
|
||||
gspin.connect("value-changed", (widget) => {
|
||||
onChange?.(widget.value);
|
||||
});
|
||||
}
|
||||
this.add_suffix(gspin);
|
||||
this.set_css_classes(["spin"]);
|
||||
this.activatable_widget = gspin;
|
||||
}
|
||||
}
|
||||
|
||||
export class DropDownRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* Name of the gsetting key to bind to
|
||||
*/
|
||||
bind;
|
||||
|
||||
/**
|
||||
* @type {'b'|'y'|'n'|'q'|'i'|'u'|'x'|'t'|'h'|'d'|'s'|'o'|'g'|'?'|'a'|'m'}
|
||||
* - b: the type string of G_VARIANT_TYPE_BOOLEAN; a boolean value.
|
||||
* - y: the type string of G_VARIANT_TYPE_BYTE; a byte.
|
||||
* - n: the type string of G_VARIANT_TYPE_INT16; a signed 16 bit integer.
|
||||
* - q: the type string of G_VARIANT_TYPE_UINT16; an unsigned 16 bit integer.
|
||||
* - i: the type string of G_VARIANT_TYPE_INT32; a signed 32 bit integer.
|
||||
* - u: the type string of G_VARIANT_TYPE_UINT32; an unsigned 32 bit integer.
|
||||
* - x: the type string of G_VARIANT_TYPE_INT64; a signed 64 bit integer.
|
||||
* - t: the type string of G_VARIANT_TYPE_UINT64; an unsigned 64 bit integer.
|
||||
* - h: the type string of G_VARIANT_TYPE_HANDLE; a signed 32 bit value that, by convention, is used as an index into an array of file descriptors that are sent alongside a D-Bus message.
|
||||
* - d: the type string of G_VARIANT_TYPE_DOUBLE; a double precision floating point value.
|
||||
* - s: the type string of G_VARIANT_TYPE_STRING; a string.
|
||||
* - o: the type string of G_VARIANT_TYPE_OBJECT_PATH; a string in the form of a D-Bus object path.
|
||||
* - g: the type string of G_VARIANT_TYPE_SIGNATURE; a string in the form of a D-Bus type signature.
|
||||
* - ?: the type string of G_VARIANT_TYPE_BASIC; an indefinite type that is a supertype of any of the basic types.
|
||||
* - v: the type string of G_VARIANT_TYPE_VARIANT; a container type that contain any other type of value.
|
||||
* - a: used as a prefix on another type string to mean an array of that type; the type string “ai”, for example, is the type of an array of signed 32-bit integers.
|
||||
* - m: used as a prefix on another type string to mean a “maybe”, or “nullable”, version of that type; the type string “ms”, for example, is the type of a value that maybe contains a string, or maybe contains nothing.
|
||||
*/
|
||||
type;
|
||||
|
||||
selected = 0;
|
||||
|
||||
/** @type {{name: string; id: string}[]} */
|
||||
items;
|
||||
|
||||
model = new Gtk.StringList();
|
||||
|
||||
/** @type {Gtk.DropDown} */
|
||||
dropdown;
|
||||
|
||||
constructor({ title, settings, bind, items, subtitle = "", type }) {
|
||||
super({ title, subtitle });
|
||||
this.settings = settings;
|
||||
this.items = items;
|
||||
this.bind = bind;
|
||||
this.type = type ?? this.settings.get_value(bind)?.get_type() ?? "?";
|
||||
this.#build();
|
||||
this.add_suffix(this.dropdown);
|
||||
this.add_suffix(new ResetButton({ settings, bind, onReset: () => this.reset() }));
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.dropdown.selected = 0;
|
||||
this.selected = 0;
|
||||
}
|
||||
|
||||
#build() {
|
||||
for (const { name, id } of this.items) {
|
||||
this.model.append(name);
|
||||
if (this.#get() === id) this.selected = this.items.findIndex((x) => x.id === id);
|
||||
}
|
||||
const { model, selected } = this;
|
||||
this.dropdown = new Gtk.DropDown({ valign: Gtk.Align.CENTER, model, selected });
|
||||
this.dropdown.connect("notify::selected", () => this.#onSelected());
|
||||
this.activatable_widget = this.dropdown;
|
||||
}
|
||||
|
||||
#onSelected() {
|
||||
this.selected = this.dropdown.selected;
|
||||
const { id } = this.items[this.selected];
|
||||
Logger.debug("setting", id, this.selected);
|
||||
this.#set(this.bind, id);
|
||||
}
|
||||
|
||||
static #settingsTypes = {
|
||||
b: "boolean",
|
||||
y: "byte",
|
||||
n: "int16",
|
||||
q: "uint16",
|
||||
i: "int32",
|
||||
u: "uint",
|
||||
x: "int64",
|
||||
t: "uint64",
|
||||
d: "double",
|
||||
s: "string",
|
||||
o: "objv",
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} x
|
||||
*/
|
||||
#get(x = this.bind) {
|
||||
const methodName = `get_${DropDownRow.#settingsTypes[this.type] ?? "value"}`;
|
||||
return this.settings[methodName]?.(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} x
|
||||
* @param {unknown} y
|
||||
*/
|
||||
#set(x, y) {
|
||||
const methodName = `set_${DropDownRow.#settingsTypes[this.type] ?? "value"}`;
|
||||
Logger.log(`${methodName}(${x}, ${y})`);
|
||||
return this.settings[methodName]?.(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearButton extends Gtk.Button {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
constructor({ settings = undefined, bind = undefined, onClear }) {
|
||||
super({
|
||||
icon_name: "edit-clear-symbolic",
|
||||
tooltip_text: _("Clear shortcut"),
|
||||
css_classes: ["flat", "circular"],
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
this.connect("clicked", () => {
|
||||
onClear?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ResetButton extends Gtk.Button {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
constructor({ settings = undefined, bind = undefined, onReset }) {
|
||||
super({
|
||||
icon_name: "edit-undo-symbolic",
|
||||
tooltip_text: _("Reset to default"),
|
||||
css_classes: ["flat", "circular"],
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
this.connect("clicked", () => {
|
||||
settings?.reset(bind);
|
||||
onReset?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveButton extends Gtk.Button {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
constructor({ item, parent, onRemove }) {
|
||||
super({
|
||||
icon_name: "edit-delete-symbolic",
|
||||
tooltip_text: _("Remove Item"),
|
||||
css_classes: ["flat", "circular"],
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
this.connect("clicked", () => {
|
||||
onRemove?.(item, parent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class EntryRow extends Adw.EntryRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ title, settings, bind, map }) {
|
||||
super({ title });
|
||||
this.connect("changed", () => {
|
||||
const text = this.get_text();
|
||||
if (typeof text === "string")
|
||||
if (map) {
|
||||
map.to(settings, bind, text);
|
||||
} else {
|
||||
settings.set_string(bind, text);
|
||||
}
|
||||
});
|
||||
const current = map ? map.from(settings, bind) : settings.get_string(bind);
|
||||
this.set_text(current ?? "");
|
||||
this.add_suffix(
|
||||
new ClearButton({
|
||||
settings,
|
||||
bind,
|
||||
onClear: () => {
|
||||
this.set_text("");
|
||||
},
|
||||
})
|
||||
);
|
||||
this.add_suffix(
|
||||
new ResetButton({
|
||||
settings,
|
||||
bind,
|
||||
onReset: () => {
|
||||
this.set_text((map ? map.from(settings, bind) : settings.get_string(bind)) ?? "");
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RadioRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
static orientation = Gtk.Orientation.HORIZONTAL;
|
||||
|
||||
static spacing = 3;
|
||||
|
||||
static valign = Gtk.Align.CENTER;
|
||||
|
||||
constructor({ title, subtitle = "", settings, bind, options }) {
|
||||
super({ title, subtitle });
|
||||
const current = settings.get_string(bind);
|
||||
const labels = Object.fromEntries(Object.entries(options).map(([k, v]) => [v, k]));
|
||||
const { orientation, spacing, valign } = RadioRow;
|
||||
const hbox = new Gtk.Box({ orientation, spacing, valign });
|
||||
let group;
|
||||
for (const [key, label] of Object.entries(options)) {
|
||||
const toggle = new Gtk.ToggleButton({ label, ...(group && { group }) });
|
||||
group ||= toggle;
|
||||
toggle.active = key === current;
|
||||
toggle.set_css_classes(["flat"]);
|
||||
toggle.connect("clicked", () => {
|
||||
if (toggle.active) {
|
||||
settings.set_string(bind, labels[toggle.label]);
|
||||
}
|
||||
});
|
||||
hbox.append(toggle);
|
||||
}
|
||||
this.add_suffix(hbox);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveItemRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ title, subtitle = "", onRemove = undefined }) {
|
||||
super({ title, subtitle });
|
||||
const rmbutton = new RemoveButton({
|
||||
item: subtitle,
|
||||
parent: this,
|
||||
onRemove: onRemove,
|
||||
});
|
||||
|
||||
this.add_suffix(rmbutton);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user