Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The goal #1

Open
NeatNit opened this issue Feb 9, 2023 · 2 comments
Open

The goal #1

NeatNit opened this issue Feb 9, 2023 · 2 comments

Comments

@NeatNit
Copy link
Owner

NeatNit commented Feb 9, 2023

opacity mockup
This is the goal I'm going for

@NeatNit
Copy link
Owner Author

NeatNit commented Feb 10, 2023

Not far off!

opacity-slider-demo

I have no idea why the menu is twice as wide as soon as the slider is added. I also want to try to make the "Opacity: XX%" text centered. Ideally I'd put it on the side of the slider like the mock-up, but I don't think that's possible with the existing PopupSliderMenuItem and it's a bad idea to duplicate it and change the required code (thus not benefiting from future updates).

@ghost
Copy link

ghost commented Jun 29, 2023

I've been playing a little with your code out of boredom and I guess it may have improved a bit. GTK menus are kinda screwed up, I had the same issue in Applet PYE while working on it, and ended up forking the PopupSliderMenuItem code in order to achieve the desired result - which is much what you want here.
For now I just used a little hacking on the official PopupSliderMenuItem. It's not perfect but arguably a little better than above. Here's the code:

const PopupMenu = imports.ui.popupMenu;
const WindowMenu = imports.ui.windowMenu;
const St = imports.gi.St;

let isEnabled = false, isInstalled = false;
let base_buildMenu, my_buildMenu;
let uuid = "[email protected]"

// implement opacity slider that does the same thing as the scroll action:
// https://github.com/linuxmint/muffin/blob/e78b8335f3113c38b7611193ae55ff3e14820961/src/core/window.c#L8803-L8817

function init(metadata) {
	uuid = metadata.uuid;
}

let log = function(text, mode="") {
	if (mode == "w")
		global.logWarning("[" + uuid + "]: " + text);
	else if (mode == "e")
		global.logError("[" + uuid + "]: " + text);
	else global.log("[" + uuid + "]: " + text);
}

// add a slider
function addSlider(to_menu, value, callback) {
	// variant of WindowMenu.prototype.addAction, but that adds a slider instead of a MnemonicLeftOrnamentedMenuItem
	let menuItem = new PopupMenu.PopupSliderMenuItem(value);
	menuItem.removeActor(menuItem._slider);
	let icon = new St.Icon({icon_name: "display-brightness", icon_type: St.IconType.SYMBOLIC, icon_size: 12, reactive: false});
//	let label = new St.Label({ text: _("Opacity") + ": ???", reactive: false, important: true });
	let label = new St.Label({ text: "???", reactive: false, important: true });
	let style = "text-align: right; border: none; padding: 1px 3px 1px 5px; margin: 0px;";
	label.set_style(style);
	menuItem.addActor(icon, {span: 1, expand: false, align: St.Align.START});
	menuItem.addActor(menuItem._slider, {span: 0, expand: true});
	menuItem.addActor(label, {span: -1, expand: false, align: St.Align.START});
	menuItem.actor.set_style("margin: 0; spacing: 0; span: 0; expand: false;");
	to_menu.addMenuItem(menuItem, to_menu.numMenuItems - 2);

	menuItem.connect('value-changed', callback );

	// this._items is used for mnemonics
	// before you uncomment the next line you must extend PopupSliderMenuItem and implement stuff
	// see windowMenu.js
	//this._items.push(menuItem);

	return menuItem;
}

const opacity_max = 255;
const opacity_min = 26;

function sliderValueFromOpacity(opacity) {
	return Math.max(0, Math.min(1, (opacity - opacity_min) / (opacity_max - opacity_min)));
}

function opacityFromSliderValue(value) {
	return Math.max(0, Math.min(255, Math.round( (value * (opacity_max-opacity_min) ) + opacity_min )));
}

function install() {
	// install ourselves by diverting the WindowMenu internal function
	// hacky as hell!
	let slider = null;
	base_buildMenu = WindowMenu.WindowMenu.prototype._buildMenu;
	my_buildMenu = function(window, ...args) {
		// Creating a right-click menu

		// Build the normal menu
		const retval = base_buildMenu.call(this, window, ...args);

		// If we're enabled, create our extra slider
		if (isEnabled) {
			// Add separator
			// The position this.numMenuItems-2 adds the item before the last item ("Close")
			this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(), this.numMenuItems - 2);

			// Function to update the label's text
			function updateLabel(opacity) {
				let c = slider.actor-get_children(); // c[0]=icon, c[1]=slider, c[2]=label
//				c[2].text = _("Opacity") + ": " + Math.floor(100*opacity/255).toString() + "%";
				c[2].text = Math.floor(100*opacity/255).toString() + "%";
			}

			// Get current opacity
			const initial_opacity = window.get_opacity();

			// Create the slider
			slider = addSlider.call(this, this, sliderValueFromOpacity(initial_opacity), (sl, value) => {
				// convert the value range (0 to 1) to an acceptable opacity

				const opacity = opacityFromSliderValue(value);

				window.set_opacity(opacity);

				updateLabel(opacity);
			} );

			// Use current opacity to set label text
			updateLabel(initial_opacity);

			// For both new menu entries, add empty ornament
			// TODO: add icon too. See MnemonicLeftOrnamentedMenuItem._init in windowMenu.js
//			slider.addActor(new St.Bin(), {position: 0});
		}

		return retval;
	}

	log("overriding WindowMenu.prototype._buildMenu");
	WindowMenu.WindowMenu.prototype._buildMenu = my_buildMenu;
	isInstalled = true;
}

function tryUninstall() {
	// Try to revert the method we overrode to what it was before
	// We can only do this if no one else overrode it after us
	// (unless they also cleaned up after themselves like we're doing here)
	if (WindowMenu.WindowMenu.prototype._buildMenu == my_buildMenu) {
		// We are clear to undo!
		log("reverting WindowMenu.prototype._buildMenu to former value")
		WindowMenu.WindowMenu.prototype._buildMenu = base_buildMenu;
		isInstalled = false;

		// allow garbage collection
		base_buildMenu = undefined;
		my_buildMenu = undefined;
	} else {
		log("cannot revert WindowMenu.prototype._buildMenu: it has been overridden elsewhere", "w")
	}
}

function enable() {
	isEnabled = true;

	if (!isInstalled) {
		install();
	}
}

function disable() {
	isEnabled = false;

	if (isInstalled) {
		tryUninstall();
	}
}

Screenshot from 2023-06-29 20-04-02

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant