nixos/modules/home/ags/app/widget/Dropdown.tsx

129 lines
3.9 KiB
TypeScript

import { App, Astal, Gdk, Gtk } from "astal/gtk3"
import GLib from "gi://GLib"
import { Variable, bind } from "astal"
const DURATION = 180
interface DropdownProps {
gdkmonitor: Gdk.Monitor
target: Gtk.Widget
open: Variable<boolean>
width?: number
vertical?: boolean
className?: string
// cap the popup height to the screen, scrolling any overflow
maxHeight?: number
children: Array<JSX.Element>
}
export default function Dropdown({
gdkmonitor,
target,
open,
width = 260,
vertical = true,
className = "",
maxHeight,
children,
}: DropdownProps) {
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor
const GAP = 8
const child = (
<box
className={`dropdown ${className}`}
vertical={vertical}
widthRequest={width}
halign={Gtk.Align.START}
valign={Gtk.Align.START}
>
{maxHeight !== undefined ? (
<scrollable
hexpand
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
maxContentHeight={maxHeight}
propagateNaturalHeight
>
<box vertical>{children}</box>
</scrollable>
) : (
children
)}
</box>
)
function reposition() {
const toplevel = target.get_toplevel()
const [, tx, ty] = target.translate_coordinates(toplevel, 0, 0)
const { width: bw, height: bh } = target.get_allocation()
const { width: sw } = gdkmonitor.get_geometry()
const x = Math.min(Math.max(tx + bw / 2 - width / 2, GAP), sw - width - GAP)
const y = ty + bh + GAP
child.marginStart = Math.round(x)
child.marginTop = Math.round(y)
}
// the window outlives the animation: it hides once the slide-out
// revealer finished, and hiding the window resolves to open=false
const winVisible = Variable(false)
let closeTimer: number | undefined
open.subscribe((v) => {
if (closeTimer !== undefined) {
GLib.source_remove(closeTimer)
closeTimer = undefined
}
if (v) {
reposition()
winVisible.set(true)
} else if (winVisible.get()) {
closeTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, DURATION + 20, () => {
winVisible.set(false)
closeTimer = undefined
return GLib.SOURCE_REMOVE
})
}
})
return (
<window
className="popup-window"
application={App}
gdkmonitor={gdkmonitor}
namespace="ags-bar-popup"
layer={Astal.Layer.OVERLAY}
keymode={Astal.Keymode.EXCLUSIVE}
anchor={TOP | BOTTOM | LEFT | RIGHT}
exclusivity={Astal.Exclusivity.IGNORE}
visible={bind(winVisible)}
onNotifyVisible={(self) => {
if (!self.visible && open.get()) open.set(false)
}}
onButtonPressEvent={(self, e) => {
const event = e as unknown as Gdk.Event
const [, px, py] = event.get_coords()
const { x, y, width: w, height: h } = child.get_allocation()
const xOut = px < x || px > x + w
const yOut = py < y || py > y + h
if (xOut || yOut) open.set(false)
}}
onKeyPressEvent={(_, e) => {
const event = e as unknown as Gdk.Event
if (event.get_keyval()[1] === Gdk.KEY_Escape) open.set(false)
}}
>
<revealer
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
transitionDuration={DURATION}
revealChild={bind(open)}
>
{child}
</revealer>
</window>
)
}