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
This commit is contained in:
Rafał Dzięgiel
2021-02-16 12:08:45 +01:00
parent 6448012edd
commit 530f60bce9
6 changed files with 167 additions and 71 deletions

View File

@@ -18,10 +18,7 @@ radio {
.gtk402 .osd trough highlight { .gtk402 .osd trough highlight {
border-color: inherit; border-color: inherit;
} }
.osd headerbar { .osdheaderbar button {
background: transparent;
}
.osd headerbar button {
border: transparent; border: transparent;
} }
.adwrounded.csd { .adwrounded.csd {

View File

@@ -27,7 +27,7 @@ class ClapperApp extends AppBase
window.isClapperApp = true; window.isClapperApp = true;
window.add_css_class('nobackground'); window.add_css_class('nobackground');
const clapperWidget = new Widget(window); const clapperWidget = new Widget();
window.set_child(clapperWidget); window.set_child(clapperWidget);
const dummyHeaderbar = new Gtk.HeaderBar({ const dummyHeaderbar = new Gtk.HeaderBar({

View File

@@ -4,11 +4,15 @@ const { HeaderBarBase } = imports.src.headerbarBase;
var HeaderBar = GObject.registerClass( var HeaderBar = GObject.registerClass(
class ClapperHeaderBar extends HeaderBarBase 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() _onFloatButtonClicked()

View File

@@ -1,95 +1,193 @@
const { GObject, Gtk, Pango } = imports.gi; const { GObject, Gtk } = imports.gi;
const Misc = imports.src.misc; const Misc = imports.src.misc;
var HeaderBarBase = GObject.registerClass( var HeaderBarBase = GObject.registerClass(
class ClapperHeaderBarBase extends Gtk.HeaderBar class ClapperHeaderBarBase extends Gtk.Box
{ {
_init(window) _init()
{ {
super._init({ super._init({
can_focus: false, 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 clapperPath = Misc.getClapperPath();
const uiBuilder = Gtk.Builder.new_from_file( const uiBuilder = Gtk.Builder.new_from_file(
`${clapperPath}/ui/clapper.ui` `${clapperPath}/ui/clapper.ui`
); );
this.add_css_class('noborder'); this.menuWidget = new Gtk.Box({
this.set_title_widget(this._createWidgetForWindow(window)); 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', icon_name: 'open-menu-symbolic',
valign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER,
}); });
const mainMenuModel = uiBuilder.get_object('mainMenu'); const mainMenuModel = uiBuilder.get_object('mainMenu');
const mainMenuPopover = new HeaderBarPopover(mainMenuModel); const mainMenuPopover = new HeaderBarPopover(mainMenuModel);
mainMenuButton.set_popover(mainMenuPopover); this.menuButton.set_popover(mainMenuPopover);
mainMenuButton.add_css_class('circular'); this.menuButton.add_css_class('circular');
this.pack_start(mainMenuButton); this.menuWidget.append(this.menuButton);
const buttonsBox = new Gtk.Box({ this.extraButtonsBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL, orientation: Gtk.Orientation.HORIZONTAL,
valign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER,
}); });
buttonsBox.add_css_class('linked'); this.extraButtonsBox.add_css_class('linked');
const floatButton = new Gtk.Button({ const floatButton = new Gtk.Button({
icon_name: 'go-bottom-symbolic', icon_name: 'go-bottom-symbolic',
}); });
floatButton.add_css_class('circular'); floatButton.add_css_class('circular');
floatButton.connect('clicked', this._onFloatButtonClicked.bind(this)); floatButton.connect('clicked',
buttonsBox.append(floatButton); this._onFloatButtonClicked.bind(this)
);
this.extraButtonsBox.append(floatButton);
const fullscreenButton = new Gtk.Button({ const fullscreenButton = new Gtk.Button({
icon_name: 'view-fullscreen-symbolic', icon_name: 'view-fullscreen-symbolic',
}); });
fullscreenButton.add_css_class('circular'); 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.spacerWidget = new Gtk.Box({
this.pack_start(buttonsBox); 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; if(this.isMenuOnLeft === isOnLeft)
this.subtitleLabel.visible = (subtitle !== null); return;
if(subtitle) if(isOnLeft) {
this.subtitleLabel.label = subtitle; this.menuWidget.reorder_child_after(
this.extraButtonsBox, this.menuButton
);
}
else {
this.menuWidget.reorder_child_after(
this.menuButton, this.extraButtonsBox
);
} }
_createWidgetForWindow(window) this.isMenuOnLeft = isOnLeft;
}
setMaximized(isMaximized)
{ {
const box = new Gtk.Box({ if(this.isMaximized === isMaximized)
orientation: Gtk.Orientation.VERTICAL, 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, valign: Gtk.Align.CENTER,
}); });
button.add_css_class('circular');
this.titleLabel = new Gtk.Label({ const action = (name === 'maximize')
halign: Gtk.Align.CENTER, ? 'window.toggle-maximized'
single_line_mode: true, : 'window.' + name;
ellipsize: Pango.EllipsizeMode.END,
width_chars: 5,
});
this.titleLabel.add_css_class('title');
this.titleLabel.set_parent(box);
window.bind_property('title', this.titleLabel, 'label', button.connect('clicked',
GObject.BindingFlags.SYNC_CREATE this._onWindowButtonActivate.bind(this, action)
); );
this.subtitleLabel = new Gtk.Label({ return button;
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 box; _onWindowButtonActivate(action)
{
} }
_onFloatButtonClicked() _onFloatButtonClicked()

View File

@@ -91,7 +91,7 @@ class ClapperCustomRevealer extends Gtk.Revealer
var RevealerTop = GObject.registerClass( var RevealerTop = GObject.registerClass(
class ClapperRevealerTop extends CustomRevealer class ClapperRevealerTop extends CustomRevealer
{ {
_init(window) _init()
{ {
super._init({ super._init({
transition_duration: REVEAL_TIME, transition_duration: REVEAL_TIME,
@@ -133,7 +133,7 @@ class ClapperRevealerTop extends CustomRevealer
revealerBox.add_css_class('osd'); revealerBox.add_css_class('osd');
revealerBox.add_css_class('reavealertop'); revealerBox.add_css_class('reavealertop');
this.headerBar = new HeaderBar(window); this.headerBar = new HeaderBar();
revealerBox.append(this.headerBar); revealerBox.append(this.headerBar);
this.revealerGrid = new Gtk.Grid({ this.revealerGrid = new Gtk.Grid({

View File

@@ -12,7 +12,7 @@ const { settings } = Misc;
var Widget = GObject.registerClass( var Widget = GObject.registerClass(
class ClapperWidget extends Gtk.Grid class ClapperWidget extends Gtk.Grid
{ {
_init(window) _init()
{ {
super._init(); super._init();
@@ -30,7 +30,7 @@ class ClapperWidget extends Gtk.Grid
this.needsTracksUpdate = true; this.needsTracksUpdate = true;
this.overlay = new Gtk.Overlay(); this.overlay = new Gtk.Overlay();
this.revealerTop = new Revealers.RevealerTop(window); this.revealerTop = new Revealers.RevealerTop();
this.revealerBottom = new Revealers.RevealerBottom(); this.revealerBottom = new Revealers.RevealerBottom();
this.controls = new Controls(); this.controls = new Controls();
@@ -109,6 +109,7 @@ class ClapperWidget extends Gtk.Grid
if(this.fullscreenMode === isFullscreen) if(this.fullscreenMode === isFullscreen)
return; return;
debug('changing fullscreen mode');
this.fullscreenMode = isFullscreen; this.fullscreenMode = isFullscreen;
const root = this.get_root(); const root = this.get_root();
@@ -132,6 +133,8 @@ class ClapperWidget extends Gtk.Grid
this.player.playOnFullscreen = false; this.player.playOnFullscreen = false;
this.player.play(); this.player.play();
} }
debug(`interface in fullscreen mode: ${isFullscreen}`);
} }
_saveWindowSize(size) _saveWindowSize(size)
@@ -160,8 +163,8 @@ class ClapperWidget extends Gtk.Grid
if(!mediaInfo) if(!mediaInfo)
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
/* Set titlebar media title and path */ /* Set titlebar media title */
this.updateTitles(mediaInfo); this.updateTitle(mediaInfo);
/* Show/hide position scale on LIVE */ /* Show/hide position scale on LIVE */
const isLive = mediaInfo.is_live(); const isLive = mediaInfo.is_live();
@@ -270,25 +273,18 @@ class ClapperWidget extends Gtk.Grid
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
} }
updateTitles(mediaInfo) updateTitle(mediaInfo)
{ {
let title = mediaInfo.get_title(); let title = mediaInfo.get_title();
let subtitle = this.player.playlistWidget.getActiveFilename();
if(!title) { if(!title) {
const subtitle = this.player.playlistWidget.getActiveFilename();
title = (subtitle.includes('.')) title = (subtitle.includes('.'))
? subtitle.split('.').slice(0, -1).join('.') ? subtitle.split('.').slice(0, -1).join('.')
: subtitle; : 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); this.revealerTop.setMediaTitle(title);
} }
@@ -512,15 +508,16 @@ class ClapperWidget extends Gtk.Grid
_onStateNotify(toplevel) _onStateNotify(toplevel)
{ {
const isMaximized = Boolean(
toplevel.state & Gdk.ToplevelState.MAXIMIZED
);
const isFullscreen = Boolean( const isFullscreen = Boolean(
toplevel.state & Gdk.ToplevelState.FULLSCREEN toplevel.state & Gdk.ToplevelState.FULLSCREEN
); );
const headerBar = this.revealerTop.headerBar;
if(this.fullscreenMode === isFullscreen) headerBar.setMaximized(isMaximized);
return;
this.setFullscreenMode(isFullscreen); this.setFullscreenMode(isFullscreen);
debug(`interface in fullscreen mode: ${isFullscreen}`);
} }
_onLayoutUpdate(surface, width, height) _onLayoutUpdate(surface, width, height)