control-center: add optional dual-column layout

parent 089c9da7
......@@ -39,6 +39,17 @@ scrollbar {
padding: 0;
}
/* Dual layout */
.dual-layout {
& > separator {
margin: 0 15px;
}
}
.notifications-placeholder {
opacity: 0.5;
}
/* Notifications widget */
@import "widgets/notifications";
/* Label widget */
......
......@@ -37,6 +37,28 @@ template $XimperShellNotificationCenterWidgetsNotifications: $XimperShellNotific
}
}
Box placeholder_box {
visible: false;
halign: center;
valign: center;
hexpand: true;
vexpand: true;
orientation: vertical;
spacing: 12;
styles ["notifications-placeholder"]
Image {
pixel-size: 96;
icon-name: "preferences-system-notifications-symbolic";
use-fallback: true;
}
Label {
label: _("No Notifications");
}
}
ScrolledWindow scrolled_window {
hscrollbar-policy: never;
has-frame: false;
......
......@@ -156,6 +156,13 @@ config file to be able to detect config errors
to its content, up to 'control-center-height' as ++
maximum. If false, height is fixed.
*control-center-layout* ++
type: string ++
default: single ++
values: single, dual ++
description: Panel layout. "single" is a vertical column. ++
"dual" places widgets and notifications side by side.
*control-center-width* ++
type: integer ++
default: 500 ++
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: ximper-shell-notification-center 0.1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-27 20:18+0300\n"
"POT-Creation-Date: 2026-03-27 23:10+0300\n"
"PO-Revision-Date: 2026-03-24 00:00+0300\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
......@@ -18,24 +18,24 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: src/notification/notification.vala:539
#: src/notification/notification.vala:536
msgid "Enter Text"
msgstr "Введите текст"
#: src/notification/notification.vala:551
#: src/notification/notification.vala:548
msgid "Reply"
msgstr "Ответить"
#: src/notification/notification.vala:579
#: src/notification/notification.vala:576
#, c-format
msgid "COPY \"%s\""
msgstr "КОПИРОВАТЬ \"%s\""
#: src/notification/notification.vala:621
#: src/notification/notification.vala:618
msgid "Now"
msgstr "Сейчас"
#: src/notification/notification.vala:625
#: src/notification/notification.vala:622
#, c-format
msgid "%d min ago"
msgid_plural "%d mins ago"
......@@ -43,7 +43,7 @@ msgstr[0] "%d мин. назад"
msgstr[1] "%d мин. назад"
msgstr[2] "%d мин. назад"
#: src/notification/notification.vala:629
#: src/notification/notification.vala:626
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
......@@ -51,7 +51,7 @@ msgstr[0] "%d ч. назад"
msgstr[1] "%d ч. назад"
msgstr[2] "%d ч. назад"
#: src/notification/notification.vala:633
#: src/notification/notification.vala:630
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
......@@ -162,12 +162,13 @@ msgstr "Уведомления"
msgid "Clear All"
msgstr "Очистить"
#: data/ui/widgets/notifications.blp:58
msgid "No Notifications"
msgstr "Нет уведомлений"
#: data/ui/widgets/backlight.blp:18
msgid "Brightness"
msgstr "Яркость"
#~ msgid "No Notifications"
#~ msgstr "Нет уведомлений"
#~ msgid "No active sink input"
#~ msgstr "Нет активного источника"
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: ximper-shell-notification-center\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-27 20:18+0300\n"
"POT-Creation-Date: 2026-03-27 23:10+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -18,38 +18,38 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: src/notification/notification.vala:539
#: src/notification/notification.vala:536
msgid "Enter Text"
msgstr ""
#: src/notification/notification.vala:551
#: src/notification/notification.vala:548
msgid "Reply"
msgstr ""
#: src/notification/notification.vala:579
#: src/notification/notification.vala:576
#, c-format
msgid "COPY \"%s\""
msgstr ""
#: src/notification/notification.vala:621
#: src/notification/notification.vala:618
msgid "Now"
msgstr ""
#: src/notification/notification.vala:625
#: src/notification/notification.vala:622
#, c-format
msgid "%d min ago"
msgid_plural "%d mins ago"
msgstr[0] ""
msgstr[1] ""
#: src/notification/notification.vala:629
#: src/notification/notification.vala:626
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] ""
msgstr[1] ""
#: src/notification/notification.vala:633
#: src/notification/notification.vala:630
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
......@@ -159,6 +159,10 @@ msgstr ""
msgid "Clear All"
msgstr ""
#: data/ui/widgets/notifications.blp:58
msgid "No Notifications"
msgstr ""
#: data/ui/widgets/backlight.blp:18
msgid "Brightness"
msgstr ""
......@@ -7,6 +7,7 @@
"control-center-width": 400,
"control-center-height": 740,
"control-center-dynamic-height": true,
"control-center-layout": "single",
"control-center-margin-top": 8,
"control-center-margin-bottom": 8,
"control-center-margin-right": 8,
......
......@@ -652,6 +652,12 @@ namespace XimperShellNotificationCenter {
*/
public string control_center_preferred_output { get; set; default = ""; }
/**
* Panel layout: "single" (vertical) or "dual" (two columns).
*/
public string control_center_layout {
get; set; default = "single";
}
/**
* Notification icon size, in pixels.
......
......@@ -146,6 +146,12 @@
"type": "string",
"description": "The preferred output to open the control center. Can either be the monitor connector name (ex: \"DP-1\"), or the full name, manufacturer model serial (ex: \"Acer Technologies XV272U V 503023B314202\"). If the output is not found, the currently focused one is picked"
},
"control-center-layout": {
"type": "string",
"description": "Panel layout: single (vertical column) or dual (widgets and notifications side by side)",
"default": "single",
"enum": ["single", "dual"]
},
"transition-time": {
"type": "integer",
"description": "The notification animation duration. 0 to disable",
......
......@@ -16,6 +16,10 @@ namespace XimperShellNotificationCenter {
private List<unowned Widgets.BaseWidget> widgets;
private const string[] DEFAULT_WIDGETS = { "notifications" };
private Gtk.Box ?dual_container = null;
private Gtk.Box ?left_column = null;
private Gtk.SizeGroup ?dual_size_group = null;
private string ?monitor_name = null;
public ControlCenter () {
......@@ -177,60 +181,125 @@ namespace XimperShellNotificationCenter {
return true;
}
private void remove_from_parent (Gtk.Widget child) {
var parent = child.get_parent ();
if (parent != null && parent is Gtk.Box) {
((Gtk.Box) parent).remove (child);
}
}
/** Adds all custom widgets. Removes previous widgets */
public void add_widgets () {
// Remove all widgets
widgets.foreach ((widget) => {
if (widget.get_parent () == box) {
box.remove (widget);
}
// Except for notifications. Otherwise we'd lose notifications
if (widget is Widgets.Notifications) {
return;
}
widgets.remove (widget);
});
// Detach all widgets from parents first
foreach (unowned Widgets.BaseWidget widget
in widgets) {
remove_from_parent (widget);
}
if (notifications_widget != null) {
remove_from_parent (notifications_widget);
}
// Remove dual layout container
if (dual_container != null) {
box.remove (dual_container);
dual_container = null;
left_column = null;
dual_size_group = null;
}
// Reset widgets list, keep notifications
widgets = new List<unowned Widgets.BaseWidget> ();
if (notifications_widget != null) {
widgets.append (notifications_widget);
}
string[] w = ConfigModel.instance.widgets.data;
if (w.length == 0) {
w = DEFAULT_WIDGETS;
}
bool is_dual =
ConfigModel.instance.control_center_layout
== "dual"
&& notifications_widget != null;
// Prepare target containers
Gtk.Box widgets_target;
if (is_dual) {
setup_dual_container ();
widgets_target = left_column;
} else {
widgets_target = box;
}
bool has_notifications = false;
foreach (string key in w) {
// Add the widget if it is valid
bool is_notifications;
Widgets.BaseWidget ?widget = Widgets.get_widget_from_key (
key, out is_notifications);
if (is_notifications) {
if (notifications_widget == null) {
continue;
}
if (has_notifications) {
warning ("Cannot have multiple " +
"\"notifications\" widgets! " +
"Skipping \"%s\"", key);
foreach (string key in w) {
bool is_notif;
Widgets.BaseWidget ?widget =
Widgets.get_widget_from_key (
key, out is_notif);
if (is_notif) {
if (notifications_widget == null
|| has_notifications) {
continue;
}
has_notifications = true;
notifications_widget.reload_config ();
box.append (notifications_widget);
continue;
}
if (widget == null) {
continue;
}
// Note: Copies the value into the linked list
widgets.append (widget);
widgets_target.append (widget);
}
// Add notifications
if (has_notifications) {
if (is_dual) {
var right = new Gtk.Box (
Gtk.Orientation.VERTICAL, 0);
right.add_css_class (
"notifications-column");
right.set_hexpand (true);
dual_size_group.add_widget (right);
right.append (notifications_widget);
dual_container.append (right);
} else {
box.append (notifications_widget);
}
notifications_widget.reload_config ();
}
unowned Widgets.BaseWidget cloned_widget = widgets.last ().data;
box.append (cloned_widget);
if (is_dual) {
box.append (dual_container);
}
}
private void setup_dual_container () {
dual_container = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 0);
dual_container.add_css_class ("dual-layout");
dual_container.set_vexpand (true);
dual_size_group = new Gtk.SizeGroup (
Gtk.SizeGroupMode.HORIZONTAL);
left_column = new Gtk.Box (
Gtk.Orientation.VERTICAL, 0);
left_column.add_css_class ("widgets-column");
left_column.set_hexpand (true);
dual_size_group.add_widget (left_column);
var separator = new Gtk.Separator (
Gtk.Orientation.VERTICAL);
dual_container.append (left_column);
dual_container.append (separator);
}
/** Resets the UI positions */
private void set_anchor () {
debug ("ControlCenter set_anchor");
......
......@@ -18,6 +18,8 @@ namespace XimperShellNotificationCenter.Widgets {
[GtkChild]
unowned Gtk.Button clear_all_button;
[GtkChild]
unowned Gtk.Box placeholder_box;
[GtkChild]
unowned Gtk.ScrolledWindow scrolled_window;
[GtkChild]
unowned Gtk.Viewport viewport;
......@@ -99,28 +101,44 @@ namespace XimperShellNotificationCenter.Widgets {
bool dynamic =
ConfigModel.instance
.control_center_dynamic_height;
set_vexpand (!dynamic);
bool is_dual =
ConfigModel.instance.control_center_layout
== "dual";
set_vexpand (is_dual || !dynamic);
scrolled_window.set_propagate_natural_height (
dynamic);
dynamic && !is_dual);
update_header_visibility ();
}
private void update_header_visibility () {
bool has_notis = !is_empty ();
scrolled_window.set_visible (has_notis);
bool is_dual =
ConfigModel.instance.control_center_layout
== "dual";
// Placeholder only in dual mode when empty
bool show_placeholder = is_dual && !has_notis;
placeholder_box.set_visible (show_placeholder);
scrolled_window.set_visible (!show_placeholder
&& (is_dual || has_notis));
switch (show_title) {
case "true":
header_box.set_visible (true);
title_label.set_visible (true);
clear_all_button.set_sensitive (has_notis);
clear_all_button.set_sensitive (
has_notis);
break;
case "false":
header_box.set_visible (has_notis);
title_label.set_visible (false);
break;
default: // auto
header_box.set_visible (has_notis);
header_box.set_visible (
is_dual || has_notis);
title_label.set_visible (true);
clear_all_button.set_sensitive (
has_notis);
break;
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment