Preferences: Improve the appearances tab with live preview, color scheme and accent color settings
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: Night Theme Switcher Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import GObject from "gi://GObject";
|
||||
import Gtk from "gi://Gtk";
|
||||
|
||||
export class AppearanceChooser extends Gtk.Widget {
|
||||
static {
|
||||
GObject.registerClass(
|
||||
{
|
||||
GTypeName: "AppearanceChooser",
|
||||
Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/AppearanceChooser.ui",
|
||||
Properties: {
|
||||
"accent-color": GObject.ParamSpec.string(
|
||||
"accent-color",
|
||||
"Accent color",
|
||||
"Accent color",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null,
|
||||
),
|
||||
"background-uri": GObject.ParamSpec.string(
|
||||
"background-uri",
|
||||
"Background URI",
|
||||
"URI to the background file",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null,
|
||||
),
|
||||
"color-scheme": GObject.ParamSpec.string(
|
||||
"color-scheme",
|
||||
"Color scheme",
|
||||
"Color scheme",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null,
|
||||
),
|
||||
},
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,39 @@ export class AppearancePage extends Adw.PreferencesPage {
|
||||
{
|
||||
GTypeName: "AppearancePage",
|
||||
Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/AppearancePage.ui",
|
||||
InternalChildren: ["day_button", "night_button"],
|
||||
InternalChildren: ["day_appearance_chooser", "night_appearance_chooser"],
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
constructor({ ...params } = {}) {
|
||||
constructor({ accentColorSettings, colorSchemeSettings, ...params } = {}) {
|
||||
super(params);
|
||||
const settings = new Gio.Settings({ schema: "org.gnome.desktop.background" });
|
||||
const backgroundSettings = new Gio.Settings({ schema: "org.gnome.desktop.background" });
|
||||
const interfaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" });
|
||||
|
||||
settings.bind("picture-uri", this._day_button, "uri", Gio.SettingsBindFlags.DEFAULT);
|
||||
settings.bind("picture-uri-dark", this._night_button, "uri", Gio.SettingsBindFlags.DEFAULT);
|
||||
if (accentColorSettings.get_string("day") === "unset")
|
||||
accentColorSettings.set_string("day", interfaceSettings.get_string("accent-color"));
|
||||
if (accentColorSettings.get_string("night") === "unset")
|
||||
accentColorSettings.set_string("night", interfaceSettings.get_string("accent-color"));
|
||||
|
||||
accentColorSettings.bind("day", this._day_appearance_chooser, "accent-color", Gio.SettingsBindFlags.DEFAULT);
|
||||
accentColorSettings.bind("night", this._night_appearance_chooser, "accent-color", Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
colorSchemeSettings.bind("day", this._day_appearance_chooser, "color-scheme", Gio.SettingsBindFlags.DEFAULT);
|
||||
colorSchemeSettings.bind("night", this._night_appearance_chooser, "color-scheme", Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
backgroundSettings.bind(
|
||||
"picture-uri",
|
||||
this._day_appearance_chooser,
|
||||
"background-uri",
|
||||
Gio.SettingsBindFlags.DEFAULT,
|
||||
);
|
||||
backgroundSettings.bind(
|
||||
"picture-uri-dark",
|
||||
this._night_appearance_chooser,
|
||||
"background-uri",
|
||||
Gio.SettingsBindFlags.DEFAULT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
// SPDX-FileCopyrightText: Night Theme Switcher Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import Gdk from "gi://Gdk";
|
||||
import Gio from "gi://Gio";
|
||||
import Gly from "gi://Gly";
|
||||
import GlyGtk4 from "gi://GlyGtk4";
|
||||
import GObject from "gi://GObject";
|
||||
import Graphene from "gi://Graphene";
|
||||
import Gsk from "gi://Gsk";
|
||||
import Gtk from "gi://Gtk";
|
||||
import { AccentColor } from "../enums/AccentColor.js";
|
||||
import { ColorScheme } from "../enums/ColorScheme.js";
|
||||
|
||||
Gio._promisify(Gly.Loader.prototype, "load_async", "load_finish");
|
||||
|
||||
const PREVIEW_HEIGHT = 200;
|
||||
|
||||
const PANEL_HEIGHT = 15;
|
||||
|
||||
const WINDOW_WIDTH = 160;
|
||||
const WINDOW_HEIGHT = 120;
|
||||
|
||||
const BUTTON_WIDTH = 60;
|
||||
const BUTTON_HEIGHT = 20;
|
||||
|
||||
export class AppearancePreview extends Gtk.Widget {
|
||||
/** @type {?Gdk.Texture} */
|
||||
#backgroundTexture = null;
|
||||
/** @type {Gdk.Texture} */
|
||||
#windowDarkTexture;
|
||||
/** @type {Gdk.Texture} */
|
||||
#windowLightTexture;
|
||||
|
||||
/** @type {?AccentColor} */
|
||||
#accentColor = null;
|
||||
/** @type {?string} */
|
||||
#backgroundUri = null;
|
||||
/** @type {?ColorScheme} */
|
||||
#colorScheme = null;
|
||||
|
||||
static {
|
||||
GObject.registerClass(
|
||||
{
|
||||
GTypeName: "AppearancePreview",
|
||||
CssName: "appearance-preview",
|
||||
Properties: {
|
||||
"accent-color": GObject.ParamSpec.string(
|
||||
"accent-color",
|
||||
"Accent color",
|
||||
"Accent color",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null,
|
||||
),
|
||||
"background-uri": GObject.ParamSpec.string(
|
||||
"background-uri",
|
||||
"Background URI",
|
||||
"URI to the background file",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null,
|
||||
),
|
||||
"color-scheme": GObject.ParamSpec.string(
|
||||
"color-scheme",
|
||||
"Color scheme",
|
||||
"Color scheme",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null,
|
||||
),
|
||||
},
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
constructor({ ...params } = {}) {
|
||||
super(params);
|
||||
|
||||
this.#windowDarkTexture = Gdk.Texture.new_from_resource(
|
||||
"/org/gnome/Shell/Extensions/nightthemeswitcher/preferences/assets/preview-window-dark.svg",
|
||||
);
|
||||
this.#windowLightTexture = Gdk.Texture.new_from_resource(
|
||||
"/org/gnome/Shell/Extensions/nightthemeswitcher/preferences/assets/preview-window-light.svg",
|
||||
);
|
||||
}
|
||||
|
||||
get accentColor() {
|
||||
return this.#accentColor;
|
||||
}
|
||||
|
||||
set accentColor(color) {
|
||||
if (!color || color === this.#accentColor || !Object.values(AccentColor).includes(color)) return;
|
||||
this.#accentColor = color;
|
||||
this.notify("accent-color");
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
get backgroundUri() {
|
||||
return this.#backgroundUri;
|
||||
}
|
||||
|
||||
set backgroundUri(uri) {
|
||||
if (!uri || uri === this.#backgroundUri) return;
|
||||
this.#backgroundUri = uri;
|
||||
this.notify("background-uri");
|
||||
this.#updateBackgroundTexture();
|
||||
}
|
||||
|
||||
get colorScheme() {
|
||||
return this.#colorScheme;
|
||||
}
|
||||
|
||||
set colorScheme(scheme) {
|
||||
if (!scheme || scheme === this.#colorScheme || !Object.values(ColorScheme).includes(scheme)) return;
|
||||
this.#colorScheme = scheme;
|
||||
this.notify("color-scheme");
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Gtk.Orientation} _orientation The orientation to measure.
|
||||
* @param {number} _for_size Size for the opposite of orientation.
|
||||
* @returns {[number, number, number, number]} Minimum size, natural size, minimum baseline, natural baseline.
|
||||
*/
|
||||
vfunc_measure(_orientation, _for_size) {
|
||||
return [PREVIEW_HEIGHT, PREVIEW_HEIGHT, -1, -1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Gtk.Snapshot} snapshot The snapshot to populate.
|
||||
*/
|
||||
vfunc_snapshot(snapshot) {
|
||||
const previewWidth = this.get_width();
|
||||
const previewHeight = this.get_height();
|
||||
|
||||
snapshot.push_fill(buildGskRoundedRectanglePath(previewWidth, previewHeight, 12), Gsk.FillRule.WINDING);
|
||||
|
||||
this.#snapshotBackground(snapshot, previewWidth, previewHeight);
|
||||
this.#snapshotWindows(snapshot, previewWidth, previewHeight, this.colorScheme, this.accentColor);
|
||||
this.#snapshotPanel(snapshot, previewWidth, this.colorScheme);
|
||||
|
||||
snapshot.pop();
|
||||
}
|
||||
|
||||
async #updateBackgroundTexture() {
|
||||
try {
|
||||
const loader = new Gly.Loader({ file: Gio.File.new_for_uri(this.backgroundUri) });
|
||||
const image = await loader.load_async(null);
|
||||
this.#backgroundTexture = GlyGtk4.frame_get_texture(image.next_frame());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.queue_draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Gtk.Snapshot} snapshot The snapshot to populate.
|
||||
* @param {number} width The width to populate.
|
||||
* @param {number} height The height to populate.
|
||||
*/
|
||||
#snapshotBackground(snapshot, width, height) {
|
||||
const ratio = width / height;
|
||||
if (!this.#backgroundTexture) {
|
||||
snapshot.append_color(
|
||||
new Gdk.RGBA({ red: 0.08, green: 0.235, blue: 0.533, alpha: 1 }),
|
||||
new Graphene.Rect({ origin: new Graphene.Point({ x: 0, y: 0 }), size: { width, height } }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let backgroundSnapshotWidth, backgroundSnapshotHeight;
|
||||
const backgroundRatio = this.#backgroundTexture.get_intrinsic_aspect_ratio();
|
||||
|
||||
if (backgroundRatio > ratio) {
|
||||
backgroundSnapshotWidth = height;
|
||||
backgroundSnapshotHeight = height / backgroundRatio;
|
||||
} else {
|
||||
backgroundSnapshotWidth = width * backgroundRatio;
|
||||
backgroundSnapshotHeight = width;
|
||||
}
|
||||
|
||||
const x = (width - backgroundSnapshotWidth) / 2;
|
||||
const y = (height - backgroundSnapshotHeight) / 2;
|
||||
|
||||
snapshot.save();
|
||||
snapshot.translate(new Graphene.Point({ x, y }));
|
||||
this.#backgroundTexture.snapshot(snapshot, backgroundSnapshotWidth, backgroundSnapshotHeight);
|
||||
snapshot.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Gtk.Snapshot} snapshot The snapshot to populate.
|
||||
* @param {number} width The width to populate.
|
||||
* @param {number} height The height to populate.
|
||||
* @param {ColorScheme} colorScheme The color scheme to render.
|
||||
* @param {AccentColor} accentColor The accent color to render.
|
||||
*/
|
||||
#snapshotWindows(snapshot, width, height, colorScheme, accentColor) {
|
||||
const usableHeight = height - PANEL_HEIGHT;
|
||||
|
||||
const backgroundWindowX = Math.ceil(width / 2 - WINDOW_WIDTH + WINDOW_WIDTH / 2 - width / 16);
|
||||
const backgroundWindowY = Math.ceil(
|
||||
usableHeight / 2 - WINDOW_HEIGHT + WINDOW_HEIGHT / 2 - usableHeight / 16 + PANEL_HEIGHT,
|
||||
);
|
||||
const foregroundWindowX = Math.ceil(width / 2 - WINDOW_WIDTH / 2 + width / 16);
|
||||
const foregroundWindowY = Math.ceil(usableHeight / 2 - WINDOW_HEIGHT / 2 + usableHeight / 16 + PANEL_HEIGHT);
|
||||
|
||||
switch (colorScheme) {
|
||||
case ColorScheme.DEFAULT:
|
||||
this.#snapshotWindow(snapshot, backgroundWindowX, backgroundWindowY, ColorScheme.PREFER_DARK);
|
||||
this.#snapshotWindow(snapshot, foregroundWindowX, foregroundWindowY, ColorScheme.PREFER_LIGHT);
|
||||
break;
|
||||
case ColorScheme.PREFER_DARK:
|
||||
this.#snapshotWindow(snapshot, backgroundWindowX, backgroundWindowY, ColorScheme.PREFER_DARK);
|
||||
this.#snapshotWindow(snapshot, foregroundWindowX, foregroundWindowY, ColorScheme.PREFER_DARK);
|
||||
break;
|
||||
case ColorScheme.PREFER_LIGHT:
|
||||
this.#snapshotWindow(snapshot, backgroundWindowX, backgroundWindowY, ColorScheme.PREFER_LIGHT);
|
||||
this.#snapshotWindow(snapshot, foregroundWindowX, foregroundWindowY, ColorScheme.PREFER_LIGHT);
|
||||
break;
|
||||
}
|
||||
|
||||
const buttonX = foregroundWindowX + WINDOW_WIDTH - BUTTON_WIDTH - 20;
|
||||
const buttonY = foregroundWindowY + WINDOW_HEIGHT - BUTTON_HEIGHT - 20;
|
||||
|
||||
this.#snapshotButton(snapshot, buttonX, buttonY, accentColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Gtk.Snapshot} snapshot The snapshot to populate.
|
||||
* @param {number} x The X position at which to populate.
|
||||
* @param {number} y The Y position at which to populate.
|
||||
* @param {ColorScheme} colorScheme The color scheme to render.
|
||||
*/
|
||||
#snapshotWindow(snapshot, x, y, colorScheme) {
|
||||
const texture = colorScheme === ColorScheme.PREFER_DARK ? this.#windowDarkTexture : this.#windowLightTexture;
|
||||
snapshot.save();
|
||||
snapshot.translate(new Graphene.Point({ x, y }));
|
||||
texture.snapshot(snapshot, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
snapshot.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Gtk.Snapshot} snapshot The snapshot to populate.
|
||||
* @param {number} x The X position at which to populate.
|
||||
* @param {number} y The Y position at which to populate.
|
||||
* @param {AccentColor} accentColor The accent color to render.
|
||||
*/
|
||||
#snapshotButton(snapshot, x, y, accentColor) {
|
||||
let color;
|
||||
switch (accentColor) {
|
||||
case AccentColor.BLUE:
|
||||
color = new Gdk.RGBA({ red: 0.208, green: 0.518, blue: 0.894, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.GREEN:
|
||||
color = new Gdk.RGBA({ red: 0.227, green: 0.58, blue: 0.29, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.ORANGE:
|
||||
color = new Gdk.RGBA({ red: 0.929, green: 0.357, blue: 0, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.PINK:
|
||||
color = new Gdk.RGBA({ red: 0.835, green: 0.38, blue: 0.6, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.PURPLE:
|
||||
color = new Gdk.RGBA({ red: 0.569, green: 0.255, blue: 0.675, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.RED:
|
||||
color = new Gdk.RGBA({ red: 0.902, green: 0.176, blue: 0.259, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.SLATE:
|
||||
color = new Gdk.RGBA({ red: 0.435, green: 0.514, blue: 0.588, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.TEAL:
|
||||
color = new Gdk.RGBA({ red: 0.129, green: 0.565, blue: 0.643, alpha: 1 });
|
||||
break;
|
||||
case AccentColor.YELLOW:
|
||||
color = new Gdk.RGBA({ red: 0.784, green: 0.533, blue: 0, alpha: 1 });
|
||||
break;
|
||||
}
|
||||
if (!color) return;
|
||||
snapshot.save();
|
||||
snapshot.translate(new Graphene.Point({ x, y }));
|
||||
snapshot.append_fill(buildGskRoundedRectanglePath(BUTTON_WIDTH, BUTTON_HEIGHT, 3), Gsk.FillRule.WINDING, color);
|
||||
snapshot.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Gtk.Snapshot} snapshot The snapshot to populate.
|
||||
* @param {number} width The width to populate.
|
||||
* @param {ColorScheme} colorScheme The color scheme to render.
|
||||
*/
|
||||
#snapshotPanel(snapshot, width, colorScheme) {
|
||||
const backgroundColor =
|
||||
colorScheme === ColorScheme.PREFER_LIGHT
|
||||
? new Gdk.RGBA({ red: 0.98, green: 0.98, blue: 0.984, alpha: 1 })
|
||||
: new Gdk.RGBA({ red: 0, green: 0, blue: 0, alpha: 1 });
|
||||
|
||||
snapshot.append_color(
|
||||
backgroundColor,
|
||||
new Graphene.Rect({
|
||||
origin: new Graphene.Point({ x: 0, y: 0 }),
|
||||
size: new Graphene.Size({ width, height: PANEL_HEIGHT }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a rounded rectangle path until GJS supports `Gsk.RoundedRect`.
|
||||
* @param {number} width The width of the rectangle.
|
||||
* @param {number} height The height of the rectangle.
|
||||
* @param {number} radius The radius of the corners.
|
||||
* @returns {Gsk.Path} The path.
|
||||
*/
|
||||
function buildGskRoundedRectanglePath(width, height, radius) {
|
||||
const builder = new Gsk.PathBuilder();
|
||||
builder.add_circle(new Graphene.Point({ x: radius, y: radius }), radius);
|
||||
builder.add_circle(new Graphene.Point({ x: width - radius, y: radius }), radius);
|
||||
builder.add_circle(new Graphene.Point({ x: radius, y: height - radius }), radius);
|
||||
builder.add_circle(new Graphene.Point({ x: width - radius, y: height - radius }), radius);
|
||||
builder.add_rect(
|
||||
new Graphene.Rect({
|
||||
origin: new Graphene.Point({ x: radius, y: 0 }),
|
||||
size: new Graphene.Size({ width: width - 2 * radius, height }),
|
||||
}),
|
||||
);
|
||||
builder.add_rect(
|
||||
new Graphene.Rect({
|
||||
origin: new Graphene.Point({ x: 0, y: radius }),
|
||||
size: new Graphene.Size({ width, height: height - 2 * radius }),
|
||||
}),
|
||||
);
|
||||
return builder.to_path();
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
import Adw from "gi://Adw";
|
||||
import Gdk from "gi://Gdk";
|
||||
import GdkPixbuf from "gi://GdkPixbuf";
|
||||
import Gio from "gi://Gio";
|
||||
import GLib from "gi://GLib";
|
||||
import Gly from "gi://Gly";
|
||||
import GObject from "gi://GObject";
|
||||
import Gtk from "gi://Gtk";
|
||||
|
||||
@@ -14,34 +14,14 @@ import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensio
|
||||
Gio._promisify(Gtk.FileDialog.prototype, "open", "open_finish");
|
||||
|
||||
export class BackgroundButton extends Gtk.Button {
|
||||
#uri;
|
||||
|
||||
static {
|
||||
GObject.registerClass(
|
||||
{
|
||||
GTypeName: "BackgroundButton",
|
||||
Template: "resource:///org/gnome/Shell/Extensions/nightthemeswitcher/preferences/ui/BackgroundButton.ui",
|
||||
InternalChildren: ["file_dialog", "thumbnail"],
|
||||
InternalChildren: ["file_dialog"],
|
||||
Properties: {
|
||||
uri: GObject.ParamSpec.string("uri", "URI", "URI to the background file", GObject.ParamFlags.READWRITE, null),
|
||||
thumbWidth: GObject.ParamSpec.int(
|
||||
"thumb-width",
|
||||
"Thumbnail width",
|
||||
"Width of the displayed thumbnail",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
0,
|
||||
600,
|
||||
180,
|
||||
),
|
||||
thumbHeight: GObject.ParamSpec.int(
|
||||
"thumb-height",
|
||||
"Thumbnail height",
|
||||
"Height of the displayed thumbnail",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
0,
|
||||
600,
|
||||
180,
|
||||
),
|
||||
},
|
||||
},
|
||||
this,
|
||||
@@ -50,33 +30,10 @@ export class BackgroundButton extends Gtk.Button {
|
||||
|
||||
constructor({ ...params } = {}) {
|
||||
super(params);
|
||||
this.#setupSize();
|
||||
this.#setupDropTarget();
|
||||
this.#setupFileDialog();
|
||||
}
|
||||
|
||||
get uri() {
|
||||
return this.#uri || null;
|
||||
}
|
||||
|
||||
set uri(uri) {
|
||||
if (uri === this.#uri) return;
|
||||
this.#uri = uri;
|
||||
this.notify("uri");
|
||||
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||
this.#updateThumbnail();
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
#setupSize() {
|
||||
const display = Gdk.Display.get_default();
|
||||
const monitor = display.get_monitors().get_item(0);
|
||||
if (monitor.width_mm === 0 || monitor.height_mm === 0) return;
|
||||
if (monitor.width_mm > monitor.height_mm) this.thumbHeight *= monitor.height_mm / monitor.width_mm;
|
||||
else this.thumbWidth *= monitor.width_mm / monitor.height_mm;
|
||||
}
|
||||
|
||||
#setupDropTarget() {
|
||||
const dropTarget = Gtk.DropTarget.new(Gio.File.$gtype, Gdk.DragAction.COPY);
|
||||
dropTarget.connect("drop", (_target, file, _x, _y) => {
|
||||
@@ -108,9 +65,7 @@ export class BackgroundButton extends Gtk.Button {
|
||||
}
|
||||
|
||||
#getSupportedContentTypes() {
|
||||
return GdkPixbuf.Pixbuf.get_formats()
|
||||
.flatMap((format) => format.get_mime_types())
|
||||
.concat("application/xml");
|
||||
return Gly.Loader.get_mime_types().concat("application/xml");
|
||||
}
|
||||
|
||||
#isContentTypeSupported(contentType) {
|
||||
@@ -130,61 +85,13 @@ export class BackgroundButton extends Gtk.Button {
|
||||
|
||||
async #setURIFromFileDialog() {
|
||||
try {
|
||||
this._file_dialog.initial_folder = Gio.File.new_for_uri(this.uri).get_parent();
|
||||
const file = await this._file_dialog.open(this.get_root(), null);
|
||||
this.uri = file.get_uri();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
#updateThumbnail() {
|
||||
this._thumbnail.paintable = null;
|
||||
|
||||
if (!this.uri) return;
|
||||
|
||||
const file = Gio.File.new_for_uri(this.uri);
|
||||
const contentType = Gio.content_type_guess(file.get_basename(), null)[0];
|
||||
|
||||
if (!this.#isContentTypeSupported(contentType)) return;
|
||||
|
||||
let path;
|
||||
if (Gio.content_type_equals(contentType, "application/xml")) {
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const contents = decoder.decode(file.load_contents(null)[1]);
|
||||
try {
|
||||
path = contents.match(/<file>(.+)<\/file>/m)[1];
|
||||
if (!this.#isContentTypeSupported(Gio.content_type_guess(path, null)[0])) throw new Error();
|
||||
} catch (e) {
|
||||
console.error(`No suitable background file found in ${file.get_path()}.\n${e}`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
path = file.get_path();
|
||||
}
|
||||
|
||||
const pixbuf = GdkPixbuf.Pixbuf.new_from_file(path);
|
||||
const scale =
|
||||
pixbuf.width / pixbuf.height > this.thumbWidth / this.thumbHeight
|
||||
? this.thumbHeight / pixbuf.height
|
||||
: this.thumbWidth / pixbuf.width;
|
||||
const thumbPixbuf = GdkPixbuf.Pixbuf.new(
|
||||
pixbuf.colorspace,
|
||||
pixbuf.has_alpha,
|
||||
pixbuf.bits_per_sample,
|
||||
this.thumbWidth,
|
||||
this.thumbHeight,
|
||||
);
|
||||
pixbuf.scale(
|
||||
thumbPixbuf,
|
||||
0,
|
||||
0,
|
||||
this.thumbWidth,
|
||||
this.thumbHeight,
|
||||
-(pixbuf.width * scale - this.thumbWidth) / 2,
|
||||
-(pixbuf.height * scale - this.thumbHeight) / 2,
|
||||
scale,
|
||||
scale,
|
||||
GdkPixbuf.InterpType.TILES,
|
||||
);
|
||||
|
||||
this._thumbnail.paintable = Gdk.Texture.new_for_pixbuf(thumbPixbuf);
|
||||
_getBackgroundFileName(_widget, uri) {
|
||||
return uri ? GLib.filename_display_basename(uri) : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-FileCopyrightText: Night Theme Switcher Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import GObject from "gi://GObject";
|
||||
import Gtk from "gi://Gtk";
|
||||
|
||||
import { gettext as _ } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
||||
|
||||
import { AccentColor } from "../enums/AccentColor.js";
|
||||
|
||||
export class ColorChooser extends Gtk.Widget {
|
||||
/** @type {Gtk.ToggleButton[]} */
|
||||
#buttons = [];
|
||||
|
||||
/** @type {AccentColor} */
|
||||
#activeColor;
|
||||
|
||||
static {
|
||||
GObject.registerClass(
|
||||
{
|
||||
GTypeName: "ColorChooser",
|
||||
CssName: "color-chooser",
|
||||
Properties: {
|
||||
color: GObject.ParamSpec.string(
|
||||
"active-color",
|
||||
"Active color",
|
||||
"Active color",
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null,
|
||||
),
|
||||
},
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
constructor({ ...params } = {}) {
|
||||
super({ layoutManager: new Gtk.BoxLayout({ spacing: 6 }), ...params });
|
||||
|
||||
const colors = [
|
||||
{ accentColor: AccentColor.BLUE, name: _("Blue") },
|
||||
{ accentColor: AccentColor.TEAL, name: _("Teal") },
|
||||
{ accentColor: AccentColor.GREEN, name: _("Green") },
|
||||
{ accentColor: AccentColor.YELLOW, name: _("Yellow") },
|
||||
{ accentColor: AccentColor.ORANGE, name: _("Orange") },
|
||||
{ accentColor: AccentColor.RED, name: _("Red") },
|
||||
{ accentColor: AccentColor.PINK, name: _("Pink") },
|
||||
{ accentColor: AccentColor.PURPLE, name: _("Purple") },
|
||||
{ accentColor: AccentColor.SLATE, name: _("Slate") },
|
||||
];
|
||||
|
||||
let toggleGroup;
|
||||
for (const color of colors) {
|
||||
const button = new Gtk.ToggleButton({
|
||||
tooltipText: color.name,
|
||||
cssClasses: ["circular", color.accentColor],
|
||||
active: this.activeColor === color.accentColor,
|
||||
});
|
||||
|
||||
if (!toggleGroup) toggleGroup = button;
|
||||
else button.group = toggleGroup;
|
||||
|
||||
button.connect("toggled", () => {
|
||||
if (!button.active) return;
|
||||
this.activeColor = color.accentColor;
|
||||
});
|
||||
|
||||
this.#buttons.push(button);
|
||||
|
||||
button.set_parent(this);
|
||||
}
|
||||
}
|
||||
|
||||
get activeColor() {
|
||||
return this.#activeColor;
|
||||
}
|
||||
|
||||
set activeColor(color) {
|
||||
if (!color || color === this.#activeColor || !Object.values(AccentColor).includes(color)) return;
|
||||
this.#activeColor = color;
|
||||
this.notify("active-color");
|
||||
this.#updateButtons();
|
||||
}
|
||||
|
||||
#updateButtons() {
|
||||
this.#buttons.forEach((button) => {
|
||||
button.active = button.cssClasses.includes(this.activeColor);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user