Skip to content

Commit 81eba1c

Browse files
committed
Add PopupDialog widget - draggable, re-focusable dialog.
Their behavior is of a window that can be repositioned by gragging any 'background' area. - It remains above all normal windows, and on all workspaces. - Multiple dialogs can be open at one time, and focus can be lost and regained by clicking. - These windows do *not* participate in alt-tab or expo/overview modes. Details: - baseDialog.js: adds a base class for shared code between modal and popup dialog. - popupDialog.js: Draggable wrapper for Dialog.Dialog, to use in place of ModalDialog. - The 'popup-dialog' css class can be used to help differeniation. ref: https://github.com/orgs/linuxmint/discussions/774
1 parent 7fd1830 commit 81eba1c

9 files changed

Lines changed: 530 additions & 185 deletions

File tree

data/theme/cinnamon-sass/widgets/_dialogs.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,10 @@
258258
}
259259
}
260260
}
261+
262+
// Popup dialog (non-modal, draggable)
263+
.popup-dialog {
264+
.dialog {
265+
box-shadow: 0 -1px $accent_bg_color;
266+
}
267+
}

js/ui/baseDialog.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
2+
3+
const Clutter = imports.gi.Clutter;
4+
const St = imports.gi.St;
5+
const GObject = imports.gi.GObject;
6+
7+
const Params = imports.misc.params;
8+
9+
var State = Object.freeze({
10+
OPENED: 0,
11+
CLOSED: 1,
12+
OPENING: 2,
13+
CLOSING: 3,
14+
});
15+
16+
var BaseDialog = GObject.registerClass({
17+
Properties: {
18+
'state': GObject.ParamSpec.int(
19+
'state', 'Dialog state', 'state',
20+
GObject.ParamFlags.READABLE,
21+
Math.min(...Object.values(State)),
22+
Math.max(...Object.values(State)),
23+
State.CLOSED)
24+
},
25+
Signals: { 'opened': {}, 'closed': {} }
26+
}, class BaseDialog extends St.Widget {
27+
28+
_init(stWidgetProps, params) {
29+
super._init(stWidgetProps);
30+
31+
params = Params.parse(params, {
32+
destroyOnClose: true,
33+
});
34+
35+
this._state = State.CLOSED;
36+
this._destroyOnClose = params.destroyOnClose;
37+
38+
this.openAndCloseTime = 100;
39+
if (!global.settings.get_boolean("desktop-effects-workspace")) {
40+
this.openAndCloseTime = 0;
41+
}
42+
43+
this._initialKeyFocus = null;
44+
this._initialKeyFocusDestroyId = 0;
45+
}
46+
47+
_initDialogLayout(dialogLayout) {
48+
this.dialogLayout = dialogLayout;
49+
this.contentLayout = dialogLayout.contentLayout;
50+
this.buttonLayout = dialogLayout.buttonLayout;
51+
global.focus_manager.add_group(dialogLayout);
52+
}
53+
54+
get state() {
55+
return this._state;
56+
}
57+
58+
_setState(state) {
59+
if (this._state == state)
60+
return;
61+
62+
this._state = state;
63+
this.notify('state');
64+
}
65+
66+
clearButtons() {
67+
this.dialogLayout.clearButtons();
68+
}
69+
70+
setButtons(buttons) {
71+
this.clearButtons();
72+
73+
for (let buttonInfo of buttons) {
74+
this.addButton(buttonInfo);
75+
}
76+
}
77+
78+
addButton(buttonInfo) {
79+
return this.dialogLayout.addButton(buttonInfo);
80+
}
81+
82+
setInitialKeyFocus(actor) {
83+
if (this._initialKeyFocusDestroyId)
84+
this._initialKeyFocus.disconnect(this._initialKeyFocusDestroyId);
85+
86+
this._initialKeyFocus = actor;
87+
this._initialKeyFocusDestroyId = 0;
88+
89+
if (!actor)
90+
return;
91+
92+
this._initialKeyFocusDestroyId = actor.connect('destroy', () => {
93+
this._initialKeyFocus = null;
94+
this._initialKeyFocusDestroyId = 0;
95+
});
96+
}
97+
98+
_grabInitialKeyFocus() {
99+
let focus = this._initialKeyFocus || this.dialogLayout.initialKeyFocus;
100+
if (focus)
101+
focus.grab_key_focus();
102+
}
103+
104+
_animateOpen() {
105+
this._setState(State.OPENING);
106+
107+
this.dialogLayout.opacity = 255;
108+
this.opacity = 0;
109+
this.show();
110+
this.ease({
111+
opacity: 255,
112+
duration: this.openAndCloseTime,
113+
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
114+
onComplete: () => {
115+
this._setState(State.OPENED);
116+
this.emit('opened');
117+
}
118+
});
119+
}
120+
121+
_animateClose() {
122+
this._setState(State.CLOSING);
123+
124+
this.ease({
125+
opacity: 0,
126+
duration: this.openAndCloseTime,
127+
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
128+
onComplete: () => {
129+
this._setState(State.CLOSED);
130+
this.hide();
131+
this.emit('closed');
132+
133+
if (this._destroyOnClose)
134+
this.destroy();
135+
}
136+
});
137+
}
138+
});

js/ui/dialog.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Dialog extends St.Widget {
4545
vertical: true
4646
});
4747

48-
// modal dialogs are fixed width and grow vertically; set the request
48+
// Dialogs are fixed width and grow vertically; set the request
4949
// mode accordingly so wrapped labels are handled correctly during
5050
// size requests.
5151
this._dialog.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);

0 commit comments

Comments
 (0)