From 151744defba482226f6a224aa4ce9d4e2e5cb02b Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 21 Apr 2025 18:06:19 -0300 Subject: [PATCH] :sparkles: ags(control-center/pages): better page widget, add `PageButton` widget for better looking buttons --- ags/widget/control-center/pages/Bluetooth.ts | 186 +++++++++--------- ags/widget/control-center/pages/Network.ts | 185 ++++++++--------- ags/widget/control-center/pages/NightLight.ts | 88 ++++----- ags/widget/control-center/pages/Page.ts | 134 +++++++++---- 4 files changed, 316 insertions(+), 277 deletions(-) diff --git a/ags/widget/control-center/pages/Bluetooth.ts b/ags/widget/control-center/pages/Bluetooth.ts index efe614b..cf8f280 100644 --- a/ags/widget/control-center/pages/Bluetooth.ts +++ b/ags/widget/control-center/pages/Bluetooth.ts @@ -1,23 +1,24 @@ -import { bind, Variable } from "astal"; +import { bind } from "astal"; import { Gtk, Widget } from "astal/gtk3"; import AstalBluetooth from "gi://AstalBluetooth"; -import { Page } from "./Page"; +import { Page, PageButton } from "./Page"; import { Separator, SeparatorProps } from "../../Separator"; -const watchingDevices = new Variable(false); - -export const BluetoothPage: Page = new Page({ - title: "Bluetooth Devices", +export const BluetoothPage: (() => Page) = () => new Page({ + id: "bluetooth", + title: "Bluetooth", description: "Manage your Bluetooth devices and add new ones.", className: "bluetooth", - headerButtons: () => [ + headerButtons: [ new Widget.Button({ className: "discover nf", - label: watchingDevices(watching => !watching ? '󰑓' : '󰙦'), - tooltipText: watchingDevices(watching => !watching ? "Start discovering" : "Stop discovery"), + label: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) => + !discovering ? '󰑓' : '󰙦'), + tooltipText: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) => + !discovering ? "Start discovering" : "Stop discovery"), onClick: () => { - if(watchingDevices.get()) { + if(AstalBluetooth.get_default().adapter.discovering) { stopBluetoothDevicesWatch(); return; } @@ -27,64 +28,87 @@ export const BluetoothPage: Page = new Page({ } as Widget.ButtonProps) ], onClose: () => stopBluetoothDevicesWatch(), - pageChild: () => new Widget.Box({ - className: "connections", - orientation: Gtk.Orientation.VERTICAL, - expand: true, - hexpand: true, - children: [ - new Widget.Box({ - className: "paired", - orientation: Gtk.Orientation.VERTICAL, - visible: bind(AstalBluetooth.get_default(), "devices").as((devs) => - devs.filter(dev => dev.paired || dev.connected).length > 0), - children: bind(AstalBluetooth.get_default(), "devices").as((devs: Array) => { - const connectedDevices = devs.filter((dev: AstalBluetooth.Device) => dev.connected || dev.paired) + children: [ + new Widget.Box({ + className: "adapters", + visible: bind(AstalBluetooth.get_default(), "adapters").as((adapters) => + adapters.length > 1), + children: bind(AstalBluetooth.get_default(), "adapters").as((adapters) => [ + new Widget.Label({ + className: "sub-header", + label: "Adapters" + } as Widget.LabelProps), + ...adapters.map(adapter => + PageButton({ + title: adapter.alias ?? "Adapter", + icon: "bluetooth-active-symbolic", + onClick: () => AstalBluetooth.get_default(), + }) + ) + ] + ) + } as Widget.BoxProps), + new Widget.Box({ + className: "connections", + orientation: Gtk.Orientation.VERTICAL, + hexpand: true, + children: [ + new Widget.Box({ + className: "paired", + orientation: Gtk.Orientation.VERTICAL, + visible: bind(AstalBluetooth.get_default(), "devices").as((devs) => + devs.filter(dev => dev.paired || dev.connected).length > 0), + children: bind(AstalBluetooth.get_default(), "devices").as((devs: Array) => { + const connectedDevices = devs.filter((dev: AstalBluetooth.Device) => dev.connected || dev.paired) - return [ - new Widget.Label({ - className: "sub-header", - label: "Paired Devices", - xalign: 0, - } as Widget.LabelProps), - ...connectedDevices.map((dev: AstalBluetooth.Device) => DeviceWidget(dev)) - ] - }) - } as Widget.BoxProps), - new Widget.Box({ - className: "discovered", - orientation: Gtk.Orientation.VERTICAL, - visible: bind(AstalBluetooth.get_default(), "devices").as((devs) => - devs.filter((dev) => !dev.connected && !dev.paired).length > 0), - children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array) => { - const discoveredDevices = devices.filter((dev: AstalBluetooth.Device) => !dev.connected && !dev.paired); + return [ + new Widget.Label({ + className: "sub-header", + label: "Paired Devices", + xalign: 0, + } as Widget.LabelProps), + ...connectedDevices.map((dev: AstalBluetooth.Device) => DeviceWidget(dev)) + ] + }) + } as Widget.BoxProps), + new Widget.Box({ + className: "discovered", + orientation: Gtk.Orientation.VERTICAL, + visible: bind(AstalBluetooth.get_default(), "devices").as((devs) => + devs.filter((dev) => !dev.connected && !dev.paired).length > 0), + children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array) => { + const discoveredDevices = devices.filter((dev: AstalBluetooth.Device) => !dev.connected && !dev.paired); - return [ - new Widget.Label({ - className: "sub-header", - label: "Others", - xalign: 0 - } as Widget.LabelProps), - ...discoveredDevices.map((dev: AstalBluetooth.Device) => DeviceWidget(dev)) - ] - }) - } as Widget.BoxProps), - Separator({ - size: .2, - orientation: Gtk.Orientation.VERTICAL, - alpha: .2 - } as SeparatorProps), - new Widget.Button({ - className: "more", - label: "More settings", - xalign: 0 - } as Widget.ButtonProps) - ] - } as Widget.BoxProps) + return [ + new Widget.Label({ + className: "sub-header", + label: "Others", + xalign: 0 + } as Widget.LabelProps), + ...discoveredDevices.map((dev: AstalBluetooth.Device) => DeviceWidget(dev)) + ] + }) + } as Widget.BoxProps), + Separator({ + size: .2, + orientation: Gtk.Orientation.VERTICAL, + alpha: .2 + } as SeparatorProps), + new Widget.Button({ + className: "more", + label: "More settings", + xalign: 0 + } as Widget.ButtonProps) + ] + } as Widget.BoxProps) + ] }); function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget { - return new Widget.Button({ + return PageButton({ + className: bind(dev, "connected").as((connected) => connected ? "connected" : ""), + title: bind(dev, "alias").as(alias => alias ?? "Unknown Device"), + icon: dev.icon ?? "bluetooth-active-symbolic", onClick: () => { if(dev.paired) { dev.connected ? @@ -99,46 +123,30 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget { dev.disconnect_device(null) : dev.connect_device(null); }, - className: bind(dev, "connected").as((connected) => connected ? "connected" : ""), - child: new Widget.Box({ - className: "device", - orientation: Gtk.Orientation.HORIZONTAL, - expand: true, + endWidget: new Widget.Box({ + visible: bind(dev, "batteryPercentage").as((bat: number) => + bat <= -1 ? false : true), children: [ - new Widget.Icon({ - className: "icon", - icon: bind(dev, "icon").as((icon: string) => - icon ? icon : "bluetooth-active-symbolic"), - css: "font-size: 20px; margin-right: 6px;" - } as Widget.IconProps), new Widget.Label({ - className: "alias", - halign: Gtk.Align.START, - hexpand: true, - label: bind(dev, "alias").as((alias) => alias.split('-').length === 6 ? - `Unknown (${alias})` : alias) - } as Widget.LabelProps), - new Widget.Label({ - className: "battery", halign: Gtk.Align.END, - visible: bind(dev, "batteryPercentage").as((bat: number) => - bat <= -1 ? false : true), label: bind(dev, "batteryPercentage").as((bat: number) => - `󰁹 ${Math.floor(bat * 100)}%`) - } as Widget.LabelProps) + `${Math.floor(bat * 100)}%`) + } as Widget.LabelProps), + new Widget.Icon({ + icon: "battery-symbolic", + css: "font-size: 18px; margin-left: 6px;" + } as Widget.IconProps) ] } as Widget.BoxProps) - } as Widget.ButtonProps) + }); } function watchNewDevices(): void { - watchingDevices.set(true); !AstalBluetooth.get_default().adapter.discovering && AstalBluetooth.get_default().adapter.start_discovery(); } export function stopBluetoothDevicesWatch(): void { - watchingDevices.set(false); AstalBluetooth.get_default().adapter.discovering && AstalBluetooth.get_default().adapter.stop_discovery(); } diff --git a/ags/widget/control-center/pages/Network.ts b/ags/widget/control-center/pages/Network.ts index 3d3a947..2622721 100644 --- a/ags/widget/control-center/pages/Network.ts +++ b/ags/widget/control-center/pages/Network.ts @@ -1,5 +1,5 @@ import { Gtk, Widget } from "astal/gtk3"; -import { Page } from "./Page"; +import { Page, PageButton } from "./Page"; import AstalNetwork from "gi://AstalNetwork"; import { bind } from "astal"; import NM from "gi://NM"; @@ -7,10 +7,11 @@ import { Separator, SeparatorProps } from "../../Separator"; import { Windows } from "../../../windows"; import AstalHyprland from "gi://AstalHyprland?version=0.1"; -export const PageNetwork = new Page({ +export const PageNetwork: (() => Page) = () => new Page({ + id: "network", title: "Network", className: "network", - headerButtons: () => [ + headerButtons: [ new Widget.Button({ className: "reload nf", label: "󰑓", @@ -21,107 +22,89 @@ export const PageNetwork = new Page({ onClick: () => AstalNetwork.get_default().wifi.scan() } as Widget.ButtonProps) ], - pageChild: () => new Widget.Box({ - expand: true, - orientation: Gtk.Orientation.VERTICAL, - children: [ - new Widget.Box({ - className: "devices", - hexpand: true, - orientation: Gtk.Orientation.VERTICAL, - visible: bind(AstalNetwork.get_default().get_client(), "devices").as((devs) => devs.length > 0), - children: bind(AstalNetwork.get_default().get_client(), "devices").as((devices) => { - devices = devices.filter(dev => dev.interface !== "lo"); + children: [ + new Widget.Box({ + className: "devices", + hexpand: true, + orientation: Gtk.Orientation.VERTICAL, + visible: bind(AstalNetwork.get_default().get_client(), "devices").as((devs) => devs.length > 0), + children: bind(AstalNetwork.get_default().get_client(), "devices").as((devices) => { + devices = devices.filter(dev => dev.interface !== "lo"); - return [ - new Widget.Label({ - label: "Devices", - xalign: 0, - className: "sub-header", - } as Widget.LabelProps), - ...devices.filter(device => device.real).map(dev => new Widget.Button({ + return [ + new Widget.Label({ + label: "Devices", + xalign: 0, + className: "sub-header", + } as Widget.LabelProps), + ...devices.filter(device => device.real).map(dev => PageButton({ className: "device", - child: bind(AstalNetwork.get_default(), "client").as((client) => new Widget.Box({ - children: [ - new Widget.Icon({ - className: "icon", - icon: bind(dev, "deviceType").as(deviceType => - deviceType === NM.DeviceType.WIFI ? - "network-wireless-symbolic" - : "network-wired-symbolic"), - css: "font-size: 20px; margin-right: 6px;" + icon: bind(dev, "deviceType").as(deviceType => + deviceType === NM.DeviceType.WIFI ? + "network-wireless-symbolic" + : "network-wired-symbolic"), + title: bind(dev, "interface").as(iface => iface ?? "Interface"), + extraButtons: [ + new Widget.Button({ + image: new Widget.Icon({ + icon: "view-more-symbolic" } as Widget.IconProps), - new Widget.Label({ - className: "interface name", - xalign: 0, - hexpand: true, - label: bind(dev, "interface").as(iface => iface ?? "Unknown Interface") - } as Widget.LabelProps), - new Widget.Icon({ - icon: "object-select-symbolic", - halign: Gtk.Align.END, - visible: bind(client, "primaryConnection").as((primaryConn) => - primaryConn.devices.filter(device => device === dev)?.[0]).as(Boolean) - } as Widget.IconProps), - new Widget.EventBox({ - child: new Widget.Icon({ - icon: "view-more-symbolic" - } as Widget.IconProps), - onClick: () => { - Windows.close("control-center"); - AstalHyprland.get_default().dispatch("exec", - `[animationstyle gnomed] nm-connection-editor --edit ${dev.get_udi()}`); - } - } as Widget.EventBoxProps) - ] - } as Widget.BoxProps)) - } as Widget.ButtonProps)) - ] - }) - } as Widget.BoxProps), - new Widget.Box({ - className: "wireless-aps", - visible: bind(AstalNetwork.get_default(), "primary").as((primary) => primary === AstalNetwork.Primary.WIFI), - hexpand: true, - orientation: Gtk.Orientation.VERTICAL, - children: AstalNetwork.get_default().wifi ? bind(AstalNetwork.get_default().wifi.get_device(), "accessPoints").as((aps) => - aps.map(ap => new Widget.Button({ - hexpand: true, - onClick: () => console.log("connect to " + ap.get_ssid().toArray().toString()), // TODO I don't have a WiFi board :( - child: new Widget.Box({ - hexpand: true, - children: [ - new Widget.Icon({ - halign: Gtk.Align.START, - className: "icon", - icon: "network-wireless-signal-excellent-symbolic" - } as Widget.IconProps), - new Widget.Label({ - className: "ssid", - halign: Gtk.Align.START, - label: ap.ssid.toArray().toString() - } as Widget.LabelProps), - new Widget.Label({ - className: "status", - } as Widget.LabelProps) + onClick: () => { + Windows.close("control-center"); + AstalHyprland.get_default().dispatch("exec", + `[animationstyle gnomed; float] nm-connection-editor --edit ${ + dev.activeConnection?.connection.get_uuid() + }`); + } + } as Widget.ButtonProps) ] - } as Widget.BoxProps) - } as Widget.ButtonProps))) : [], - } as Widget.BoxProps), - Separator({ - orientation: Gtk.Orientation.VERTICAL, - alpha: .2, - size: .2 - } as SeparatorProps), - new Widget.Button({ - label: "More settings", - xalign: 0, - onClick: () => { - Windows.close("control-center"); - AstalHyprland.get_default().dispatch("exec", "[animationstyle gnomed] nm-connection-editor"); - } - } as Widget.ButtonProps) - ] - } as Widget.BoxProps) + }) + ) + ] + }) + } as Widget.BoxProps), + new Widget.Box({ + className: "wireless-aps", + visible: bind(AstalNetwork.get_default(), "primary").as((primary) => primary === AstalNetwork.Primary.WIFI), + hexpand: true, + orientation: Gtk.Orientation.VERTICAL, + children: AstalNetwork.get_default().wifi ? bind(AstalNetwork.get_default().wifi.get_device(), "accessPoints").as((aps) => + aps.map(ap => new Widget.Button({ + hexpand: true, + onClick: () => console.log("connect to " + ap.get_ssid().toArray().toString()), // TODO I don't have a WiFi board :( + child: new Widget.Box({ + hexpand: true, + children: [ + new Widget.Icon({ + halign: Gtk.Align.START, + className: "icon", + icon: "network-wireless-signal-excellent-symbolic" + } as Widget.IconProps), + new Widget.Label({ + className: "ssid", + halign: Gtk.Align.START, + label: ap.ssid.toArray().toString() + } as Widget.LabelProps), + new Widget.Label({ + className: "status", + } as Widget.LabelProps) + ] + } as Widget.BoxProps) + } as Widget.ButtonProps))) : [], + } as Widget.BoxProps), + Separator({ + orientation: Gtk.Orientation.VERTICAL, + alpha: .2, + size: .2 + } as SeparatorProps), + new Widget.Button({ + label: "More settings", + xalign: 0, + onClick: () => { + Windows.close("control-center"); + AstalHyprland.get_default().dispatch("exec", "[animationstyle gnomed] nm-connection-editor"); + } + } as Widget.ButtonProps) + ] }); diff --git a/ags/widget/control-center/pages/NightLight.ts b/ags/widget/control-center/pages/NightLight.ts index fcc71df..5946db1 100644 --- a/ags/widget/control-center/pages/NightLight.ts +++ b/ags/widget/control-center/pages/NightLight.ts @@ -1,54 +1,50 @@ -import { Gtk, Widget } from "astal/gtk3"; +import { Widget } from "astal/gtk3"; import { Page, PageProps } from "./Page"; import { bind } from "astal"; import { NightLight } from "../../../scripts/nightlight"; import { addSliderMarksFromMinMax } from "../../../scripts/widget-utils"; -export const PageNightLight = new Page({ +export const PageNightLight: (() => Page) = () => new Page({ + id: "night-light", title: "Night Light", description: "Control night light and gamma filters", - - pageChild: () => new Widget.Box({ - hexpand: true, - className: "night-light", - orientation: Gtk.Orientation.VERTICAL, - children: [ - new Widget.Label({ - className: "sub-header", - label: "Temperature (blue-light filter)", - xalign: 0 - } as Widget.LabelProps), - new Widget.Slider({ - className: "temperature", - setup: (slider) => { - slider.value = NightLight.getDefault().temperature; - addSliderMarksFromMinMax(slider, 5, "{}K"); - }, - value: bind(NightLight.getDefault(), "temperature"), - tooltipText: bind(NightLight.getDefault(), "temperature").as((temp) => `${temp}K`), - min: 1000, - max: bind(NightLight.getDefault(), "maxTemperature"), - onDragged: (slider) => - NightLight.getDefault().temperature = (Math.floor(slider.value)), - } as Widget.SliderProps), - new Widget.Label({ - className: "sub-header", - label: "Gamma (light filter)", - css: "margin-top: 6px;", - xalign: 0 - } as Widget.LabelProps), - new Widget.Slider({ - className: "gamma", - setup: (slider) => { - slider.value = NightLight.getDefault().gamma; - addSliderMarksFromMinMax(slider, 5, "{}%"); - }, - value: bind(NightLight.getDefault(), "gamma"), - max: bind(NightLight.getDefault(), "maxGamma"), - tooltipText: bind(NightLight.getDefault(), "gamma").as((gamma) => `${gamma}%`), - onDragged: (slider) => - NightLight.getDefault().gamma = (Math.floor(slider.value)), - } as Widget.SliderProps) - ] - } as Widget.BoxProps) + className: "night-light", + children: [ + new Widget.Label({ + className: "sub-header", + label: "Temperature (blue-light filter)", + xalign: 0 + } as Widget.LabelProps), + new Widget.Slider({ + className: "temperature", + setup: (slider) => { + slider.value = NightLight.getDefault().temperature; + addSliderMarksFromMinMax(slider, 5, "{}K"); + }, + value: bind(NightLight.getDefault(), "temperature"), + tooltipText: bind(NightLight.getDefault(), "temperature").as((temp) => `${temp}K`), + min: 1000, + max: bind(NightLight.getDefault(), "maxTemperature"), + onDragged: (slider) => + NightLight.getDefault().temperature = (Math.floor(slider.value)), + } as Widget.SliderProps), + new Widget.Label({ + className: "sub-header", + label: "Gamma (light filter)", + css: "margin-top: 6px;", + xalign: 0 + } as Widget.LabelProps), + new Widget.Slider({ + className: "gamma", + setup: (slider) => { + slider.value = NightLight.getDefault().gamma; + addSliderMarksFromMinMax(slider, 5, "{}%"); + }, + value: bind(NightLight.getDefault(), "gamma"), + max: bind(NightLight.getDefault(), "maxGamma"), + tooltipText: bind(NightLight.getDefault(), "gamma").as((gamma) => `${gamma}%`), + onDragged: (slider) => + NightLight.getDefault().gamma = (Math.floor(slider.value)), + } as Widget.SliderProps) + ] } as PageProps); diff --git a/ags/widget/control-center/pages/Page.ts b/ags/widget/control-center/pages/Page.ts index ec7f36f..337c3bf 100644 --- a/ags/widget/control-center/pages/Page.ts +++ b/ags/widget/control-center/pages/Page.ts @@ -1,41 +1,39 @@ -import { Binding, GObject, register } from "astal"; +import { Binding, register } from "astal"; import { Gtk, Widget } from "astal/gtk3"; export type PageProps = { setup?: () => void; onClose?: () => void; onOpen?: () => void; - className?: string | Binding; - title: string | Binding; - description?: string | Binding; - headerButtons?: () => Array; - pageChild: () => Gtk.Widget; + id: string; + className?: string | Binding; + title: string | Binding; + description?: string | Binding; + headerButtons?: Array | Binding>; + orientation?: Gtk.Orientation | Binding; + child?: Gtk.Widget | Binding; + children?: Array | Binding>; }; -@register({ GTypeName: "Page" }) -class Page extends GObject.Object { - readonly #props: PageProps; +export { Page }; - get props() { return this.#props; } +@register({ GTypeName: "Page" }) +class Page extends Widget.Box { + readonly #id: string; + #title: string | Binding; + #description: string | undefined | Binding; + + public get title() { return this.#title; } + public get description() { return this.#description; } + public get id() { return this.#id; } constructor(props: PageProps) { - super(); - this.#props = props; - } - - public getHeaderButtons(): (Array|null) { - return this.props.headerButtons ? - this.props.headerButtons() - : null; - } - - public getPage(): Gtk.Widget { - return new Widget.Box({ - className: (this.props.className instanceof Binding) ? - this.props.className.as((clsName: (string|undefined)) => `page ${ clsName || "" }`) : `page ${this.#props.className || ""}`, - orientation: Gtk.Orientation.VERTICAL, + super({ hexpand: true, - setup: this.props.setup, + orientation: Gtk.Orientation.VERTICAL, + className: (props.className instanceof Binding) ? + props.className.as((clsName) => `page ${ clsName ?? "" }`) + : `page ${props.className ?? ""}`, children: [ new Widget.Box({ className: "header", @@ -43,22 +41,24 @@ class Page extends GObject.Object { hexpand: true, children: [ new Widget.Box({ - className: "title", + className: "top", children: [ new Widget.Label({ hexpand: true, className: "title", truncate: true, - visible: (this.props.title instanceof Binding) ? - this.props.title.as(Boolean) - : (this.props.title ? true : false), - label: this.props.title, + visible: (props.title instanceof Binding) ? + props.title.as(Boolean) + : (props.title ? true : false), + label: props.title, halign: Gtk.Align.START } as Widget.LabelProps), new Widget.Box({ className: "button-row", - visible: Boolean(this.getHeaderButtons()), - children: this.getHeaderButtons() || undefined + visible: (props.headerButtons instanceof Binding) ? + props.headerButtons.as(Boolean) + : (props.headerButtons ? true : false), + children: props.headerButtons } as Widget.BoxProps) ] } as Widget.BoxProps), @@ -67,22 +67,74 @@ class Page extends GObject.Object { hexpand: true, truncate: true, xalign: 0, - visible: (this.props.description instanceof Binding) ? - this.props.description.as(Boolean) - : this.props.description ? true : false, - label: this.props.description + visible: (props.description instanceof Binding) ? + props.description.as(Boolean) + : props.description ? true : false, + label: props.description } as Widget.LabelProps), ] } as Widget.BoxProps), new Widget.Box({ className: "content", - orientation: Gtk.Orientation.VERTICAL, + orientation: props.orientation ?? Gtk.Orientation.VERTICAL, expand: true, - setup: (_) => _.add(this.props.pageChild()) + setup: props.setup, + child: props.child, + children: props.children } as Widget.BoxProps) ] - } as Widget.BoxProps); + }); + + this.#id = props.id; + this.#title = props.title; + this.#description = props.description; } } -export { Page }; +export function PageButton(props: { + className?: string | Binding; + icon?: string | Binding; + title: string | Binding; + endWidget?: Gtk.Widget; + extraButtons?: Array; + onClick?: (self: Widget.Button) => void; +}): Gtk.Widget { + return new Widget.Box({ + setup: (self) => { + self.add(new Widget.Button({ + onClick: props.onClick, + className: props.className, + hexpand: true, + child: new Widget.Box({ + className: "page-button", + orientation: Gtk.Orientation.HORIZONTAL, + expand: true, + setup: (box) => { + box.set_children([ + new Widget.Icon({ + className: "icon", + icon: props.icon, + visible: props.icon, + css: "font-size: 20px; margin-right: 6px;" + } as Widget.IconProps), + new Widget.Label({ + className: "title", + halign: Gtk.Align.START, + hexpand: true, + truncate: true, + label: props.title + } as Widget.LabelProps) + ]); + + props.endWidget && box.add(props.endWidget); + } + } as Widget.BoxProps) + } as Widget.ButtonProps)); + + props.extraButtons && self.add(new Widget.Box({ + className: "button-row extra-buttons", + children: props.extraButtons + } as Widget.BoxProps)); + } + } as Widget.BoxProps); +}