quick-settings: add widget with tile base class and grid layout

parent 4ecd858c
......@@ -396,5 +396,7 @@ notificationwindow, blankwindow {
@import "widgets/slider";
/* Backlight widget */
@import "widgets/backlight";
/* Quick Settings widget */
@import "widgets/quick-settings";
/* Inhibitors widget */
@import "widgets/inhibitors";
.widget-quick-settings {
padding: 0;
}
.quick-settings-row {
margin: 6px 0;
}
.quick-settings-tile {
border-radius: 999px;
overflow: hidden;
.tile-toggle {
padding: 4px 12px;
border: none;
box-shadow: none;
background: transparent;
}
.tile-arrow {
padding: 4px;
min-width: 24px;
border: none;
box-shadow: none;
background: transparent;
}
}
.quick-settings-submenu {
border-radius: var(--border-radius);
margin: 4px 0;
padding: 4px;
}
......@@ -34,6 +34,9 @@ namespace XimperShellNotificationCenter.Widgets {
case "buttons-grid":
widget = new ButtonsGrid (suffix);
break;
case "quick-settings":
widget = new QuickSettings (suffix);
break;
case "slider":
widget = new Slider (suffix);
break;
......
namespace XimperShellNotificationCenter.Widgets {
private struct TileRow {
Gtk.Revealer revealer;
QuickSettingsTile ?active_submenu_tile;
}
private static QuickSettingsTile ?create_tile (
string type_id, Json.Object ?cfg) {
QuickSettingsTile ?tile = null;
switch (type_id) {
case "dnd":
tile = new DndTile (cfg);
break;
case "dark-mode":
tile = new DarkModeTile (cfg);
break;
case "power-profiles":
tile = new PowerProfilesTile (cfg);
break;
case "command":
tile = new CommandTile (cfg);
break;
default:
warning ("Unknown quick-settings tile: %s",
type_id);
return null;
}
if (!tile.is_available ()) {
return null;
}
return tile;
}
public class QuickSettings : BaseWidget {
public override string widget_name {
get {
return "quick-settings";
}
}
List<QuickSettingsTile> tiles = new List<QuickSettingsTile> ();
TileRow[] rows = {};
int columns = 2;
public QuickSettings (string suffix) {
base (suffix);
set_orientation (Gtk.Orientation.VERTICAL);
Json.Object ?config = get_config (this);
if (config == null) {
return;
}
Json.Array ?tile_configs = get_prop_array (
config, "tiles");
if (tile_configs == null || tile_configs.get_length () == 0) {
return;
}
// Create tiles
for (uint i = 0; i < tile_configs.get_length (); i++) {
Json.Object ?tile_cfg =
tile_configs.get_object_element (i);
if (tile_cfg == null) {
continue;
}
string ?type_id =
tile_cfg.get_string_member_with_default (
"type", null);
if (type_id == null) {
continue;
}
QuickSettingsTile ?tile =
create_tile (type_id, tile_cfg);
if (tile == null) {
continue;
}
tiles.append (tile);
}
build_grid ();
}
private void build_grid () {
int row_index = 0;
unowned List<QuickSettingsTile> iter = tiles;
var size_group = new Gtk.SizeGroup (
Gtk.SizeGroupMode.VERTICAL);
while (iter != null) {
var row_box = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 8);
row_box.set_homogeneous (true);
row_box.add_css_class ("quick-settings-row");
var revealer = new Gtk.Revealer ();
revealer.set_transition_type (
Gtk.RevealerTransitionType.SLIDE_DOWN);
revealer.set_transition_duration (200);
rows += TileRow () {
revealer = revealer,
active_submenu_tile = null,
};
int current_row = row_index;
for (int col = 0; col < columns; col++) {
if (iter == null) {
// Pad with empty box for alignment
var spacer = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 0);
row_box.append (spacer);
break;
}
unowned QuickSettingsTile tile = iter.data;
size_group.add_widget (tile);
row_box.append (tile);
// Connect submenu signals
tile.submenu_requested.connect (() => {
on_submenu_requested (tile, current_row);
});
tile.submenu_update_requested.connect (() => {
update_submenu (tile, current_row);
});
iter = iter.next;
}
append (row_box);
append (revealer);
row_index++;
}
}
private void on_submenu_requested (QuickSettingsTile tile,
int row_idx) {
// Close all other revealers
for (int i = 0; i < rows.length; i++) {
if (i != row_idx) {
rows[i].revealer.set_reveal_child (false);
rows[i].active_submenu_tile = null;
}
}
// Toggle same tile — close
if (rows[row_idx].active_submenu_tile == tile) {
rows[row_idx].revealer.set_reveal_child (false);
rows[row_idx].active_submenu_tile = null;
return;
}
// Open new submenu (always recreate for fresh data)
Gtk.Widget ?submenu = tile.create_submenu ();
if (submenu == null) {
return;
}
rows[row_idx].revealer.set_child (submenu);
rows[row_idx].revealer.set_reveal_child (true);
rows[row_idx].active_submenu_tile = tile;
}
private void update_submenu (QuickSettingsTile tile,
int row_idx) {
if (rows[row_idx].active_submenu_tile != tile) {
return;
}
Gtk.Widget ?submenu = tile.create_submenu ();
if (submenu != null) {
rows[row_idx].revealer.set_child (submenu);
}
}
public override void on_cc_visibility_change (bool val) {
if (!val) {
// Close all submenus
for (int i = 0; i < rows.length; i++) {
rows[i].revealer.set_reveal_child (false);
rows[i].active_submenu_tile = null;
}
}
foreach (unowned QuickSettingsTile tile in tiles) {
tile.on_cc_visibility_change (val);
}
}
}
}
namespace XimperShellNotificationCenter.Widgets {
public abstract class QuickSettingsTile : Gtk.Box {
protected Gtk.Button toggle_button;
protected Gtk.Image icon;
protected Gtk.Label title_label;
protected Gtk.Label subtitle_label;
protected Gtk.Button ?arrow_button = null;
private bool _active = false;
public bool active {
get { return _active; }
set {
_active = value;
if (_active) {
add_css_class ("active");
} else {
remove_css_class ("active");
}
}
}
public signal void submenu_requested ();
public signal void submenu_update_requested ();
protected QuickSettingsTile (string icon_name,
string title,
string ?subtitle = null) {
Object (orientation: Gtk.Orientation.HORIZONTAL);
set_hexpand (true);
set_valign (Gtk.Align.FILL);
add_css_class ("quick-settings-tile");
// Toggle button — main clickable area
toggle_button = new Gtk.Button ();
toggle_button.add_css_class ("tile-toggle");
toggle_button.set_hexpand (true);
var btn_content = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 8);
btn_content.set_valign (Gtk.Align.CENTER);
icon = new Gtk.Image.from_icon_name (icon_name);
icon.add_css_class ("tile-icon");
var text_box = new Gtk.Box (
Gtk.Orientation.VERTICAL, 0);
text_box.set_valign (Gtk.Align.CENTER);
text_box.set_halign (Gtk.Align.START);
title_label = new Gtk.Label (title);
title_label.set_halign (Gtk.Align.START);
title_label.set_ellipsize (Pango.EllipsizeMode.END);
title_label.add_css_class ("tile-title");
subtitle_label = new Gtk.Label (subtitle);
subtitle_label.set_halign (Gtk.Align.START);
subtitle_label.set_ellipsize (Pango.EllipsizeMode.END);
subtitle_label.add_css_class ("tile-subtitle");
subtitle_label.add_css_class ("dim-label");
subtitle_label.add_css_class ("caption");
subtitle_label.set_visible (subtitle != null);
text_box.append (title_label);
text_box.append (subtitle_label);
btn_content.append (icon);
btn_content.append (text_box);
toggle_button.set_child (btn_content);
toggle_button.clicked.connect (() => {
on_toggle ();
});
append (toggle_button);
}
protected void setup_arrow () {
add_css_class ("has-submenu");
var separator = new Gtk.Separator (
Gtk.Orientation.VERTICAL);
separator.add_css_class ("tile-separator");
arrow_button = new Gtk.Button ();
arrow_button.add_css_class ("tile-arrow");
arrow_button.set_child (
new Gtk.Image.from_icon_name (
"pan-end-symbolic"));
arrow_button.clicked.connect (() => {
submenu_requested ();
});
append (separator);
append (arrow_button);
}
public void set_subtitle (string ?text) {
if (text != null) {
subtitle_label.set_text (text);
subtitle_label.set_visible (true);
} else {
subtitle_label.set_visible (false);
}
}
public virtual bool is_available () {
return true;
}
public abstract void on_toggle ();
public virtual Gtk.Widget ?create_submenu () {
return null;
}
public virtual void on_cc_visibility_change (bool visible) {
}
}
}
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