feat: make AGS colorshell configuration fully declarative

- Add complete colorshell v2.0.3 configuration to home/ags-config/
- Disable runner plugin and NightLight tile (incompatible with NixOS)
- Customize SCSS with full opacity (no transparency)
- Add dark pale blue color scheme in home/pywal-colors/
- Configure Papirus-Dark icon theme via home-manager
- Make ~/.config/ags/ immutable and managed by Nix store
- Auto-deploy pywal colors to ~/.cache/wal/colors.json

All AGS configuration is now reproducible and version controlled.
This commit is contained in:
2025-11-04 21:36:38 +00:00
parent 593735370a
commit b2ae32a078
240 changed files with 1024921 additions and 3 deletions

View File

@@ -0,0 +1,38 @@
import { Accessor } from "ags";
import { tr } from "../i18n/intl";
import { CustomDialog } from "./CustomDialog";
import { Astal } from "ags/gtk4";
export type AskPopupProps = {
title?: string | Accessor<string>;
text: string | Accessor<string>;
cancelText?: string;
acceptText?: string;
onAccept?: () => void;
onCancel?: () => void;
};
/**
* A Popup Widget that asks yes or no to a defined promt.
* Runs onAccept() when user accepts, or else onDecline() when
* user doesn't accept / closes window.
* This window isn't usually registered in this shell windowing
* system.
*/
export function AskPopup(props: AskPopupProps): Astal.Window {
let accepted: boolean = false;
return <CustomDialog namespace={"ask-popup"} widthRequest={400} heightRequest={250}
title={props.title ?? tr("ask_popup.title")} text={props.text}
onFinish={() => !accepted && props.onCancel?.()} options={[
{ text: props.cancelText ?? tr("cancel") },
{
text: props.acceptText ?? tr("accept"),
onClick: () => {
accepted = true;
props.onAccept?.();
}
}
]} /> as Astal.Window;
}

View File

@@ -0,0 +1,73 @@
import { Astal, Gtk } from "ags/gtk4";
import { CustomDialog, getContainerCustomDialog } from "./CustomDialog";
import GLib from "gi://GLib?version=2.0";
export type AuthPopupData = {
user: string;
hidePassword: boolean;
passwd: string;
};
export function AuthPopup(props: {
/** hide password on showup. @default true */
hidePassword?: boolean;
/** icon name of the application that's requesting this popup */
iconName?: string;
/** popup body */
text: string;
/** selected user by default */
user?: string;
/** approve data after the user clicks the "grant permission" button */
onContinue: (data: AuthPopupData, reject: (message: string) => void, approve: () => void) => void;
}): Astal.Window {
const data = {
passwd: "",
user: props.user ?? GLib.get_user_name(),
hidePassword: props.hidePassword ?? true
} satisfies AuthPopupData;
const allowUserChange = props.user === undefined;
const dialog = <CustomDialog title={"Authentication"} text={props.text}
namespace={"auth-popup"} options={[
{ text: "Deny" }, // will close and call onFinish by default
{
text: "Grant permission",
onClick: () => {
if(allowUserChange)
data.user = userEntry!.text;
data.passwd = passwordEntry.text;
data.hidePassword = passwordEntry.showPeekIcon;
props.onContinue(data,
// rejected by checker function
(m) => {
// show error to user
!messageLabel.is_visible &&
messageLabel.set_visible(true);
messageLabel.set_label(m);
// clear password entry
passwordEntry.set_text("");
},
// approved by the checker
dialog.close
);
},
closeOnClick: false
}
]}>
<Gtk.Entry class={"user"} placeholderText={"User"} visible={allowUserChange} />
<Gtk.PasswordEntry class={"password"} showPeekIcon placeholderText={"Password"} />
<Gtk.Label class={"message"} label={""} />
</CustomDialog> as Astal.Window;
const messageLabel = getContainerCustomDialog(dialog).get_last_child() as Gtk.Label;
const userEntry = allowUserChange ? getContainerCustomDialog(dialog).get_first_child() as Gtk.Entry : undefined;
const passwordEntry = getContainerCustomDialog(dialog).get_first_child()?.get_next_sibling() as Gtk.PasswordEntry;
return dialog;
}

View File

@@ -0,0 +1,90 @@
import { Accessor } from "ags";
import { Astal, Gdk, Gtk } from "ags/gtk4";
import GObject from "gi://GObject?version=2.0";
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
export type BackgroundWindowProps = {
/** GtkWindow Layer */
layer?: Astal.Layer | Accessor<Astal.Layer>;
/** Monitor number where the window should open */
monitor: number | Accessor<number>;
/** Custom stylesheet used in the window. default: `background: rgba(0, 0, 0, .2)` */
css?: string | Accessor<string>;
/* Function that is called when the user releases a key in the keyboard on the window
* The `Escape` key is not passed to this function */
actionKeyPressed?: (window: Astal.Window, keyval: number, keycode: number) => void;
/** Function that is called when the user triggers a mouse-click or escape action on the window */
actionFired?: (window: Astal.Window) => void;
/** Function that is called when the user clicks on the window with primary mouse button */
actionClickPrimary?: (window: Astal.Window) => void;
/** Function that is called when the user clicks on the window with secodary mouse button */
actionClickSecondary?: (window: Astal.Window) => void;
onCloseRequest?: (window: Astal.Window) => void;
keymode?: Astal.Keymode;
exclusivity?: Astal.Exclusivity;
/** attach this window as a background for another window
* background-window will close when the attached window triggers ::close-request) */
attach?: Astal.Window;
};
/** Creates a fullscreen GtkWindow that is used for making
* the user focus on the content after this window(e.g.: AskPopup,
* Authentication Window(futurely) or any PopupWindow)
*
* @param props Properties for background-window
*
* @returns The generated background-window
*/
export function BackgroundWindow(props: BackgroundWindowProps): Astal.Window {
const conns: Map<GObject.Object, number> = new Map();
return <Astal.Window namespace={"background-window"} monitor={props.monitor} visible
layer={props.layer ?? Astal.Layer.OVERLAY} keymode={props.keymode ?? Astal.Keymode.EXCLUSIVE}
onCloseRequest={props.onCloseRequest} exclusivity={props.exclusivity ?? Astal.Exclusivity.IGNORE}
anchor={TOP | LEFT | BOTTOM | RIGHT} css={props.css ?? "background: rgba(0, 0, 0, .2);"}
$={(self) => {
const gestureClick = Gtk.GestureClick.new(),
eventControllerKey = Gtk.EventControllerKey.new();
gestureClick.set_button(0);
self.add_controller(gestureClick);
self.add_controller(eventControllerKey);
conns.set(eventControllerKey, eventControllerKey.connect("key-released",
(_, keyval, keycode) => {
if(keyval === Gdk.KEY_Escape) {
props.actionFired?.(self);
return;
}
props.actionKeyPressed?.(self, keyval, keycode);
}
));
conns.set(gestureClick, gestureClick.connect("released", (gesture) => {
if(gesture.get_current_button() === Gdk.BUTTON_PRIMARY) {
props.actionClickPrimary?.(self);
return;
}
if(gesture.get_current_button() === Gdk.BUTTON_SECONDARY) {
props.actionClickSecondary?.(self);
return;
}
props.actionFired?.(self);
}));
props.attach &&
conns.set(props.attach, (props.attach as Gtk.Widget).connect("close-request", () =>
self.close()
));
conns.set(self, self.connect("destroy", () => conns.forEach((id, obj) =>
obj.disconnect(id))));
}} /> as Astal.Window;
}

View File

@@ -0,0 +1,39 @@
import app from "ags/gtk4/app"
import { Astal, Gtk, Gdk } from "ags/gtk4"
import { execAsync } from "ags/process"
import { createPoll } from "ags/time"
export default function Bar(gdkmonitor: Gdk.Monitor) {
const time = createPoll("", 1000, "date")
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
return (
<window
visible
name="bar"
class="Bar"
gdkmonitor={gdkmonitor}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={TOP | LEFT | RIGHT}
application={app}
>
<centerbox cssName="centerbox">
<button
$type="start"
onClicked={() => execAsync("echo hello").then(console.log)}
hexpand
halign={Gtk.Align.CENTER}
>
<label label="Welcome to AGS!" />
</button>
<box $type="center" />
<menubutton $type="end" hexpand halign={Gtk.Align.CENTER}>
<label label={time} />
<popover>
<Gtk.Calendar />
</popover>
</menubutton>
</centerbox>
</window>
)
}

View File

@@ -0,0 +1,80 @@
import { Astal, Gtk } from "ags/gtk4";
import { Windows } from "../windows";
import { getPopupWindowContainer, PopupWindow } from "./PopupWindow";
import { Separator } from "./Separator";
import { tr } from "../i18n/intl";
import { Accessor, Node } from "ags";
import { transformWidget, variableToBoolean } from "../modules/utils";
export type CustomDialogProps = {
namespace?: string | Accessor<string>;
className?: string | Accessor<string>;
cssBackground?: string;
title?: string | Accessor<string>;
text?: string | Accessor<string>;
heightRequest?: number | Accessor<number>;
widthRequest?: number | Accessor<number>;
childOrientation?: Gtk.Orientation | Accessor<Gtk.Orientation>;
children?: Node;
onFinish?: () => void;
options?: Array<CustomDialogOption> | Accessor<Array<CustomDialogOption>>;
optionsOrientation?: Gtk.Orientation | Accessor<Gtk.Orientation>;
};
export interface CustomDialogOption {
onClick?: () => void;
text: string | Accessor<string>;
closeOnClick?: boolean | Accessor<boolean>;
}
function CustomDialogOption({closeOnClick = true, ...props}: CustomDialogOption & {
dialog: Astal.Window;
}) {
return <Gtk.Button class="option" hexpand label={props.text}
onClicked={() => {
props.onClick?.();
closeOnClick &&
props.dialog?.close();
}}
/>
}
export function CustomDialog({ options = [{ text: tr("accept") }], ...props}: CustomDialogProps) {
return Windows.getDefault().createWindowForFocusedMonitor((mon) => {
const popup = <PopupWindow namespace={props.namespace ?? "custom-dialog"} monitor={mon}
cssBackgroundWindow={props.cssBackground ?? "background: rgba(0, 0, 0, .3);"}
exclusivity={Astal.Exclusivity.IGNORE} layer={Astal.Layer.OVERLAY}
halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} actionClosed={() => props.onFinish?.()}
widthRequest={props.widthRequest ?? 400} heightRequest={props.heightRequest ?? 220}>
<Gtk.Box class={props.className ?? "custom-dialog-container"}
orientation={Gtk.Orientation.VERTICAL}>
<Gtk.Label class={"title"} visible={variableToBoolean(props.title)} label={props.title} />
<Gtk.Label class={"text"} visible={variableToBoolean(props.text)} label={props.text}
vexpand valign={Gtk.Align.START} />
<Gtk.Box class={"custom-children custom-child"} visible={variableToBoolean(props.children)}
orientation={props.childOrientation ?? Gtk.Orientation.VERTICAL}>
{transformWidget(props.children, (child) => child as JSX.Element)}
</Gtk.Box>
<Separator alpha={.2} visible={options && options.length > 0}
spacing={8} orientation={Gtk.Orientation.VERTICAL} />
</Gtk.Box>
</PopupWindow> as Astal.Window;
(popup.get_child()!.get_first_child()!.get_first_child() as Gtk.Box).append(
<Gtk.Box class={"options"} orientation={props.optionsOrientation ?? Gtk.Orientation.HORIZONTAL}
hexpand={true} heightRequest={38} homogeneous={true}>
{transformWidget(options, (props) => <CustomDialogOption {...props} dialog={popup} />)}
</Gtk.Box> as Gtk.Box
);
return popup;
})();
}
export function getContainerCustomDialog(dialog: Astal.Window): Gtk.Box {
return getPopupWindowContainer(dialog).get_first_child()?.get_last_child()?.get_prev_sibling() as Gtk.Box;
}

View File

@@ -0,0 +1,61 @@
import { Accessor } from "ags";
import { tr } from "../i18n/intl";
import { CustomDialog } from "./CustomDialog";
import { Astal, Gtk } from "ags/gtk4";
export type EntryPopupProps = {
title: string | Accessor<string>;
text?: string | Accessor<string>;
cancelText?: string | Accessor<string>;
acceptText?: string | Accessor<string>;
closeOnAccept?: boolean;
entryPlaceholder?: string | Accessor<string>;
onAccept: (userInput: string) => void;
onCancel?: () => void;
onFinish?: () => void;
isPassword?: boolean | Accessor<string>;
};
export function EntryPopup(props: EntryPopupProps): Astal.Window {
props.closeOnAccept = props.closeOnAccept ?? true;
let entered: boolean = false;
function onActivate(entry: Gtk.Entry|Gtk.PasswordEntry) {
props.closeOnAccept && window.close();
entered = true;
props.onAccept(entry.text);
entry.text = "";
}
const entry = props.isPassword ?
<Gtk.PasswordEntry class={"password"} xalign={.5}
placeholderText={props.entryPlaceholder}
onActivate={onActivate}
/> as Gtk.PasswordEntry
: <Gtk.Entry xalign={.5} placeholderText={props.entryPlaceholder}
onActivate={onActivate} /> as Gtk.Entry;
const window = <CustomDialog namespace={"entry-popup"} widthRequest={420}
heightRequest={220} title={props.title} text={props.text}
options={[
{
text: props.cancelText ?? tr("cancel"),
onClick: props.onCancel
},
{
text: props.acceptText ?? tr("accept"),
closeOnClick: props.closeOnAccept,
onClick: () => {
entered = true;
props.onAccept(entry.text);
entry.text = "";
}
}
]} onFinish={() => {
!entered && props.onCancel?.()
props.onFinish?.();
}}
/> as Astal.Window;
return window;
}

View File

@@ -0,0 +1,122 @@
import { Gdk, Gtk } from "ags/gtk4";
import { Separator } from "./Separator";
import { HistoryNotification, Notifications } from "../modules/notifications";
import { getAppIcon, getSymbolicIcon } from "../modules/apps";
import { escapeUnintendedMarkup, pathToURI } from "../modules/utils";
import { onCleanup } from "ags";
import GObject from "ags/gobject";
import AstalNotifd from "gi://AstalNotifd";
import Pango from "gi://Pango?version=1.0";
import GLib from "gi://GLib?version=2.0";
function getNotificationImage(notif: AstalNotifd.Notification|HistoryNotification): (string|undefined) {
const img = notif.image || notif.appIcon;
if(!img || !img.includes('/'))
return undefined;
return pathToURI(img);
}
export function NotificationWidget({ notification, actionClicked, holdOnHover, showTime, actionClose }: {
notification: AstalNotifd.Notification|number|HistoryNotification;
actionClicked?: (notif: AstalNotifd.Notification|HistoryNotification) => void;
actionClose?: (notif: AstalNotifd.Notification|HistoryNotification) => void;
holdOnHover?: boolean;
showTime?: boolean; // It's showTime :speaking_head: :boom: :bangbang:
}): Gtk.Widget {
notification = (typeof notification === "number") ?
AstalNotifd.get_default().get_notification(notification)
: notification;
const actions: Array<AstalNotifd.Action>|undefined = ((notification instanceof AstalNotifd.Notification) &&
notification.actions && notification.actions.filter(a => Boolean(a)).length > 0) ?
notification.actions?.filter(a =>
a?.id?.toLowerCase() !== "view" && a?.label?.toLowerCase() != "view"
)
: undefined;
const conns: Map<GObject.Object, Array<number>> = new Map();
onCleanup(() => conns.forEach((ids, obj) =>
ids.forEach(id => obj.disconnect(id))
));
return <Gtk.Box hexpand class={`notification ${
Notifications.getDefault().getUrgencyString(notification.urgency)
}`} orientation={Gtk.Orientation.VERTICAL} spacing={5}>
<Gtk.EventControllerMotion onEnter={() => holdOnHover &&
Notifications.getDefault().holdNotification(notification.id)
} onLeave={() => holdOnHover &&
Notifications.getDefault().releaseNotification(notification.id)
}
/>
<Gtk.GestureClick onReleased={(gesture) =>
gesture.get_current_button() === Gdk.BUTTON_PRIMARY &&
actionClicked?.(notification)
} />
<Gtk.Box class={"top"} hexpand>
<Gtk.Image class="app-icon" $={(self) => {
const icon = getSymbolicIcon(notification.appIcon ?? notification.appName) ??
getSymbolicIcon(notification.appName) ?? getAppIcon(notification.appName);
if(icon) {
self.set_from_icon_name(icon);
return;
}
self.set_visible(false);
}} />
<Gtk.Label class={"app-name"} halign={Gtk.Align.START} hexpand
label={notification.appName || "Application"} />
<Gtk.Label class={"time"} visible={showTime} xalign={1}
label={GLib.DateTime.new_from_unix_local(notification.time).format("%H:%M") ?? ""} />
<Gtk.Button halign={Gtk.Align.END} iconName={"window-close-symbolic"}
class={"close"} onClicked={() => actionClose?.(notification)}/>
</Gtk.Box>
<Separator alpha={.1} orientation={Gtk.Orientation.VERTICAL} />
<Gtk.Box class={"content"}>
{getNotificationImage(notification) &&
<Gtk.Box class={"image"} hexpand={false}
css={`background-image: url("${getNotificationImage(notification)}");`}
valign={Gtk.Align.START}
widthRequest={68}
heightRequest={64}
/>
}
<Gtk.Box class={"text"} orientation={Gtk.Orientation.VERTICAL}
vexpand>
<Gtk.Label class={"summary"} useMarkup hexpand xalign={0}
vexpand={false} ellipsize={Pango.EllipsizeMode.END} label={
escapeUnintendedMarkup(notification.summary)}
/>
<Gtk.Label class={"body"} useMarkup xalign={0} wrap hexpand
vexpand wrapMode={Pango.WrapMode.WORD_CHAR} valign={Gtk.Align.START} label={
escapeUnintendedMarkup(notification.body)}
/>
</Gtk.Box>
</Gtk.Box>
{(notification instanceof AstalNotifd.Notification) && actions && actions.length > 0 &&
<Gtk.Box class={"actions button-row"} hexpand>
{
actions.map(action =>
<Gtk.Button class={"action"} label={action.label} hexpand
onClicked={(_) => {
notification.invoke(action.id);
actionClose?.(notification);
}}
/>)
}
</Gtk.Box>
}
</Gtk.Box> as Gtk.Widget;
}

View File

@@ -0,0 +1,161 @@
import { Astal, Gdk, Gtk } from "ags/gtk4";
import { BackgroundWindow } from "./BackgroundWindow";
import { Accessor, CCProps, createComputed, createRoot, getScope } from "ags";
import { omitObjectKeys, WidgetNodeType } from "../modules/utils";
import GObject from "ags/gobject";
type PopupWindowSpecificProps = {
$?: (self: Astal.Window) => void;
children?: WidgetNodeType;
/** Stylesheet for the background of the popup-window */
cssBackgroundWindow?: string;
class?: string | Accessor<string>;
actionClosed?: (self: Astal.Window) => void|boolean;
orientation?: Gtk.Orientation | Accessor<Gtk.Orientation>;
actionClickedOutside?: (self: Astal.Window) => void;
actionKeyPressed?: (self: Astal.Window, keyval: number, keycode: number) => void;
};
export type PopupWindowProps = Pick<Partial<CCProps<Astal.Window, Astal.Window.ConstructorProps>>,
"monitor"
| "layer"
| "exclusivity"
| "marginLeft"
| "marginTop"
| "marginRight"
| "marginBottom"
| "cursor"
| "canFocus"
| "hasFocus"
| "tooltipMarkup"
| "tooltipText"
| "namespace"
| "visible"
| "widthRequest"
| "heightRequest"
| "halign"
| "valign"
| "anchor"
| "vexpand"
| "hexpand"> & PopupWindowSpecificProps;
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
export function PopupWindow(props: PopupWindowProps): GObject.Object {
props.visible ??= true;
props.layer ??= Astal.Layer.OVERLAY;
props.actionClickedOutside ??= (self: Astal.Window) => self.close();
let clickedInside: boolean = false;
return <Astal.Window {...omitObjectKeys(props, [
"actionKeyPressed",
"actionClickedOutside",
"cssBackgroundWindow",
"anchor",
"halign",
"valign",
"namespace",
"marginTop",
"widthRequest",
"heightRequest",
"visible",
"marginLeft",
"marginRight",
"marginBottom",
"hexpand",
"vexpand",
"orientation",
"actionClosed",
"$"
])} namespace={props.namespace ?? "popup-window"} class={
(props.class instanceof Accessor) ?
((props.namespace instanceof Accessor) ?
createComputed([props.class, props.namespace], (clss, namespace) =>
`popup-window ${clss} ${namespace}`)
: props.class.as(clss => `popup-window ${clss} ${props.namespace ?? ""}`))
: `popup-window ${props.class ?? ""} ${props.namespace ?? ""}`
} keymode={Astal.Keymode.EXCLUSIVE} exclusivity={props.exclusivity ?? Astal.Exclusivity.NORMAL}
anchor={TOP | LEFT | BOTTOM | RIGHT} visible={false}
onCloseRequest={(self) => props.actionClosed?.(self)}
$={(self) => {
const scope = getScope();
const conns: Map<GObject.Object, number> = new Map();
const gestureClick = Gtk.GestureClick.new();
const keyController = Gtk.EventControllerKey.new();
self.add_controller(gestureClick);
self.add_controller(keyController);
props.cssBackgroundWindow && createRoot((dispose) =>
<BackgroundWindow monitor={props.monitor ?? 0}
layer={props.layer} css={props.cssBackgroundWindow}
keymode={Astal.Keymode.NONE} attach={self}
onCloseRequest={() => dispose()}
/>
);
props.visible && self.show();
conns.set(gestureClick, gestureClick.connect("released", () => {
if(clickedInside) {
clickedInside = false;
return;
}
props.actionClickedOutside!(self);
}));
conns.set(keyController, keyController.connect("key-pressed", (_, keyval, keycode) => {
if(keyval === Gdk.KEY_Escape) {
conns.forEach((id, obj) => {
obj.disconnect(id);
});
props.actionClickedOutside!(self);
return;
}
props.actionKeyPressed?.(self, keyval, keycode);
}));
scope.onCleanup(() => conns.forEach((id, obj) => obj.disconnect(id)));
props.$?.(self);
}}>
<Gtk.Box hexpand={false} vexpand={false}>
<Gtk.Box class={"popup-window-container"} halign={props.halign}
valign={props.valign} widthRequest={props.widthRequest}
hexpand={props.hexpand} vexpand={props.vexpand}
orientation={props.orientation}
heightRequest={props.heightRequest} css={`
margin-left: ${props.marginLeft ?? 0}px;
margin-right: ${props.marginRight ?? 0}px;
margin-top: ${props.marginTop ?? 0}px;
margin-bottom: ${props.marginBottom ?? 0}px;
`} $={(self) => {
const conns = new Map<GObject.Object, number>(),
gestureClick = Gtk.GestureClick.new();
gestureClick.set_button(0);
self.add_controller(gestureClick);
conns.set(gestureClick, gestureClick.connect("released", () =>
clickedInside = true
));
conns.set(self, self.connect("destroy", () => conns.forEach((id, obj) =>
obj.disconnect(id))));
}}>
{props.children}
</Gtk.Box>
</Gtk.Box>
</Astal.Window> as Astal.Window;
}
export function getPopupWindowContainer(popupWindow: Astal.Window): Gtk.Box {
return popupWindow.get_child()!.get_first_child() as Gtk.Box;
}

View File

@@ -0,0 +1,48 @@
import { Accessor } from "ags";
import { Gtk } from "ags/gtk4";
export interface SeparatorProps {
class?: string;
alpha?: number;
cssColor?: string;
orientation?: Gtk.Orientation;
size?: number;
spacing?: number;
margin?: number;
visible?: boolean | Accessor<boolean>;
}
export function Separator(props: SeparatorProps = {
orientation: Gtk.Orientation.HORIZONTAL
}) {
props.alpha = props.alpha ?
(props.alpha > 1 ?
props.alpha / 100
: props.alpha)
: 1;
props.orientation = props.orientation ?? Gtk.Orientation.HORIZONTAL;
return <Gtk.Box name={"separator"} vexpand={props.orientation === Gtk.Orientation.HORIZONTAL}
hexpand={props.orientation === Gtk.Orientation.VERTICAL}
class={`separator ${ props.orientation === Gtk.Orientation.VERTICAL ?
"vertical" : "horizontal" }`} visible={props.visible}
css={`.vertical { padding: ${props.spacing ?? 0}px ${props.margin ?? 7}px; }
.horizontal { padding: ${props.margin ?? 4}px ${props.spacing ?? 0}px; }`}>
<Gtk.Box class={`${props.orientation === Gtk.Orientation.VERTICAL ?
"vertical"
: "horizontal"} ${props.class ?? ""}`}
vexpand={props.orientation === Gtk.Orientation.HORIZONTAL}
hexpand={props.orientation === Gtk.Orientation.VERTICAL}
css={`* {
background: ${ props.cssColor ?? "lightgray" };
opacity: ${props.alpha};
}
.horizontal { min-width: ${ props.size ?? 1 }px; }
.vertical { min-height: ${ props.size ?? 1 }px; }`}
/>
</Gtk.Box>
}