From 530f60bce923b70ec8e8bdd995c99ed42ff702f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Tue, 16 Feb 2021 12:08:45 +0100 Subject: [PATCH] Replace GTK headerbar with custom implementation This avoids D&D controllers clash and allows to freely customize how maximize, minimize and close buttons work (differently for e.g. web application) and where are they placed --- css/styles.css | 5 +- src/app.js | 2 +- src/headerbar.js | 10 ++- src/headerbarBase.js | 184 +++++++++++++++++++++++++++++++++---------- src/revealers.js | 4 +- src/widget.js | 33 ++++---- 6 files changed, 167 insertions(+), 71 deletions(-) diff --git a/css/styles.css b/css/styles.css index 8a17a6ca..74c70597 100644 --- a/css/styles.css +++ b/css/styles.css @@ -18,10 +18,7 @@ radio { .gtk402 .osd trough highlight { border-color: inherit; } -.osd headerbar { - background: transparent; -} -.osd headerbar button { +.osdheaderbar button { border: transparent; } .adwrounded.csd { diff --git a/src/app.js b/src/app.js index fc2871ab..26de73bd 100644 --- a/src/app.js +++ b/src/app.js @@ -27,7 +27,7 @@ class ClapperApp extends AppBase window.isClapperApp = true; window.add_css_class('nobackground'); - const clapperWidget = new Widget(window); + const clapperWidget = new Widget(); window.set_child(clapperWidget); const dummyHeaderbar = new Gtk.HeaderBar({ diff --git a/src/headerbar.js b/src/headerbar.js index 0866f01a..708fbc87 100644 --- a/src/headerbar.js +++ b/src/headerbar.js @@ -4,11 +4,15 @@ const { HeaderBarBase } = imports.src.headerbarBase; var HeaderBar = GObject.registerClass( class ClapperHeaderBar extends HeaderBarBase { - _init(window) + _init() { - super._init(window); + super._init(); + this.add_css_class('osdheaderbar'); + } - this.title_widget.visible = false; + _onWindowButtonActivate(action) + { + this.activate_action(action, null); } _onFloatButtonClicked() diff --git a/src/headerbarBase.js b/src/headerbarBase.js index 8823c756..ee554f17 100644 --- a/src/headerbarBase.js +++ b/src/headerbarBase.js @@ -1,95 +1,193 @@ -const { GObject, Gtk, Pango } = imports.gi; +const { GObject, Gtk } = imports.gi; const Misc = imports.src.misc; var HeaderBarBase = GObject.registerClass( -class ClapperHeaderBarBase extends Gtk.HeaderBar +class ClapperHeaderBarBase extends Gtk.Box { - _init(window) + _init() { super._init({ can_focus: false, + orientation: Gtk.Orientation.HORIZONTAL, + spacing: 6, + margin_top: 6, + margin_start: 6, + margin_end: 6, }); + this.isMaximized = false; + this.isMenuOnLeft = true; + const clapperPath = Misc.getClapperPath(); const uiBuilder = Gtk.Builder.new_from_file( `${clapperPath}/ui/clapper.ui` ); - this.add_css_class('noborder'); - this.set_title_widget(this._createWidgetForWindow(window)); + this.menuWidget = new Gtk.Box({ + orientation: Gtk.Orientation.HORIZONTAL, + valign: Gtk.Align.CENTER, + spacing: 6, + }); - const mainMenuButton = new Gtk.MenuButton({ + this.menuButton = new Gtk.MenuButton({ icon_name: 'open-menu-symbolic', valign: Gtk.Align.CENTER, }); const mainMenuModel = uiBuilder.get_object('mainMenu'); const mainMenuPopover = new HeaderBarPopover(mainMenuModel); - mainMenuButton.set_popover(mainMenuPopover); - mainMenuButton.add_css_class('circular'); - this.pack_start(mainMenuButton); + this.menuButton.set_popover(mainMenuPopover); + this.menuButton.add_css_class('circular'); + this.menuWidget.append(this.menuButton); - const buttonsBox = new Gtk.Box({ + this.extraButtonsBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, valign: Gtk.Align.CENTER, }); - buttonsBox.add_css_class('linked'); + this.extraButtonsBox.add_css_class('linked'); const floatButton = new Gtk.Button({ icon_name: 'go-bottom-symbolic', }); floatButton.add_css_class('circular'); - floatButton.connect('clicked', this._onFloatButtonClicked.bind(this)); - buttonsBox.append(floatButton); + floatButton.connect('clicked', + this._onFloatButtonClicked.bind(this) + ); + this.extraButtonsBox.append(floatButton); const fullscreenButton = new Gtk.Button({ icon_name: 'view-fullscreen-symbolic', }); fullscreenButton.add_css_class('circular'); - fullscreenButton.connect('clicked', this._onFullscreenButtonClicked.bind(this)); + fullscreenButton.connect('clicked', + this._onFullscreenButtonClicked.bind(this) + ); + this.extraButtonsBox.append(fullscreenButton); + this.menuWidget.append(this.extraButtonsBox); - buttonsBox.append(fullscreenButton); - this.pack_start(buttonsBox); + this.spacerWidget = new Gtk.Box({ + hexpand: true, + }); + + this.minimizeWidget = this._getWindowButton('minimize'); + this.maximizeWidget = this._getWindowButton('maximize'); + this.closeWidget = this._getWindowButton('close'); + + const gtkSettings = Gtk.Settings.get_default(); + this._onLayoutUpdate(gtkSettings); + + gtkSettings.connect( + 'notify::gtk-decoration-layout', + this._onLayoutUpdate.bind(this) + ); } - updateHeaderBar(title, subtitle) + setMenuOnLeft(isOnLeft) { - this.titleLabel.label = title; - this.subtitleLabel.visible = (subtitle !== null); + if(this.isMenuOnLeft === isOnLeft) + return; - if(subtitle) - this.subtitleLabel.label = subtitle; + if(isOnLeft) { + this.menuWidget.reorder_child_after( + this.extraButtonsBox, this.menuButton + ); + } + else { + this.menuWidget.reorder_child_after( + this.menuButton, this.extraButtonsBox + ); + } + + this.isMenuOnLeft = isOnLeft; } - _createWidgetForWindow(window) + setMaximized(isMaximized) { - const box = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL, + if(this.isMaximized === isMaximized) + return; + + this.maximizeWidget.icon_name = (isMaximized) + ? 'window-restore-symbolic' + : 'window-maximize-symbolic'; + + this.isMaximized = isMaximized; + } + + _onLayoutUpdate(gtkSettings) + { + const gtkLayout = gtkSettings.gtk_decoration_layout; + + this._replaceButtons(gtkLayout); + } + + _replaceButtons(gtkLayout) + { + const modLayout = gtkLayout.replace(':', ',spacer,'); + const layoutArr = modLayout.split(','); + + let lastWidget = null; + let showMinimize = false; + let showMaximize = false; + let showClose = false; + let spacerAdded = false; + + for(let name of layoutArr) { + const widget = this[`${name}Widget`]; + if(!widget) continue; + + if(!widget.parent) + this.append(widget); + else + this.reorder_child_after(widget, lastWidget); + + switch(name) { + case 'spacer': + spacerAdded = true; + break; + case 'minimize': + showMinimize = true; + break; + case 'maximize': + showMaximize = true; + break; + case 'close': + showClose = true; + break; + case 'menu': + this.setMenuOnLeft(!spacerAdded); + break; + default: + break; + } + + lastWidget = widget; + } + + this.minimizeWidget.visible = showMinimize; + this.maximizeWidget.visible = showMaximize; + this.closeWidget.visible = showClose; + } + + _getWindowButton(name) + { + const button = new Gtk.Button({ + icon_name: `window-${name}-symbolic`, valign: Gtk.Align.CENTER, }); + button.add_css_class('circular'); - this.titleLabel = new Gtk.Label({ - halign: Gtk.Align.CENTER, - single_line_mode: true, - ellipsize: Pango.EllipsizeMode.END, - width_chars: 5, - }); - this.titleLabel.add_css_class('title'); - this.titleLabel.set_parent(box); + const action = (name === 'maximize') + ? 'window.toggle-maximized' + : 'window.' + name; - window.bind_property('title', this.titleLabel, 'label', - GObject.BindingFlags.SYNC_CREATE + button.connect('clicked', + this._onWindowButtonActivate.bind(this, action) ); - this.subtitleLabel = new Gtk.Label({ - halign: Gtk.Align.CENTER, - single_line_mode: true, - ellipsize: Pango.EllipsizeMode.END, - }); - this.subtitleLabel.add_css_class('subtitle'); - this.subtitleLabel.set_parent(box); - this.subtitleLabel.visible = false; + return button; + } - return box; + _onWindowButtonActivate(action) + { } _onFloatButtonClicked() diff --git a/src/revealers.js b/src/revealers.js index 4e9611f7..49774c81 100644 --- a/src/revealers.js +++ b/src/revealers.js @@ -91,7 +91,7 @@ class ClapperCustomRevealer extends Gtk.Revealer var RevealerTop = GObject.registerClass( class ClapperRevealerTop extends CustomRevealer { - _init(window) + _init() { super._init({ transition_duration: REVEAL_TIME, @@ -133,7 +133,7 @@ class ClapperRevealerTop extends CustomRevealer revealerBox.add_css_class('osd'); revealerBox.add_css_class('reavealertop'); - this.headerBar = new HeaderBar(window); + this.headerBar = new HeaderBar(); revealerBox.append(this.headerBar); this.revealerGrid = new Gtk.Grid({ diff --git a/src/widget.js b/src/widget.js index 2530f990..6da54dfc 100644 --- a/src/widget.js +++ b/src/widget.js @@ -12,7 +12,7 @@ const { settings } = Misc; var Widget = GObject.registerClass( class ClapperWidget extends Gtk.Grid { - _init(window) + _init() { super._init(); @@ -30,7 +30,7 @@ class ClapperWidget extends Gtk.Grid this.needsTracksUpdate = true; this.overlay = new Gtk.Overlay(); - this.revealerTop = new Revealers.RevealerTop(window); + this.revealerTop = new Revealers.RevealerTop(); this.revealerBottom = new Revealers.RevealerBottom(); this.controls = new Controls(); @@ -109,6 +109,7 @@ class ClapperWidget extends Gtk.Grid if(this.fullscreenMode === isFullscreen) return; + debug('changing fullscreen mode'); this.fullscreenMode = isFullscreen; const root = this.get_root(); @@ -132,6 +133,8 @@ class ClapperWidget extends Gtk.Grid this.player.playOnFullscreen = false; this.player.play(); } + + debug(`interface in fullscreen mode: ${isFullscreen}`); } _saveWindowSize(size) @@ -160,8 +163,8 @@ class ClapperWidget extends Gtk.Grid if(!mediaInfo) return GLib.SOURCE_REMOVE; - /* Set titlebar media title and path */ - this.updateTitles(mediaInfo); + /* Set titlebar media title */ + this.updateTitle(mediaInfo); /* Show/hide position scale on LIVE */ const isLive = mediaInfo.is_live(); @@ -270,25 +273,18 @@ class ClapperWidget extends Gtk.Grid return GLib.SOURCE_REMOVE; } - updateTitles(mediaInfo) + updateTitle(mediaInfo) { let title = mediaInfo.get_title(); - let subtitle = this.player.playlistWidget.getActiveFilename(); if(!title) { + const subtitle = this.player.playlistWidget.getActiveFilename(); + title = (subtitle.includes('.')) ? subtitle.split('.').slice(0, -1).join('.') : subtitle; - - subtitle = null; } - const root = this.get_root(); - const headerbar = root.get_titlebar(); - - if(headerbar && headerbar.updateHeaderBar) - headerbar.updateHeaderBar(title, subtitle); - this.revealerTop.setMediaTitle(title); } @@ -512,15 +508,16 @@ class ClapperWidget extends Gtk.Grid _onStateNotify(toplevel) { + const isMaximized = Boolean( + toplevel.state & Gdk.ToplevelState.MAXIMIZED + ); const isFullscreen = Boolean( toplevel.state & Gdk.ToplevelState.FULLSCREEN ); + const headerBar = this.revealerTop.headerBar; - if(this.fullscreenMode === isFullscreen) - return; - + headerBar.setMaximized(isMaximized); this.setFullscreenMode(isFullscreen); - debug(`interface in fullscreen mode: ${isFullscreen}`); } _onLayoutUpdate(surface, width, height)