feat: fake screen rounding

This commit is contained in:
2 * r + 2 * t 2025-03-31 23:02:52 +11:00
parent 70a54153de
commit 8fab7d4b65
5 changed files with 150 additions and 52 deletions

View file

@ -2,6 +2,7 @@ import Bar from "@/modules/bar";
import Launcher from "@/modules/launcher"; import Launcher from "@/modules/launcher";
import NotifPopups from "@/modules/notifpopups"; import NotifPopups from "@/modules/notifpopups";
import Osds from "@/modules/osds"; import Osds from "@/modules/osds";
import ScreenCorners from "@/modules/screencorners";
import Session from "@/modules/session"; import Session from "@/modules/session";
import SideBar from "@/modules/sidebar"; import SideBar from "@/modules/sidebar";
import Calendar from "@/services/calendar"; import Calendar from "@/services/calendar";
@ -75,6 +76,7 @@ App.start({
<Session />; <Session />;
Monitors.get_default().forEach(m => <SideBar monitor={m} />); Monitors.get_default().forEach(m => <SideBar monitor={m} />);
Monitors.get_default().forEach(m => <Bar monitor={m} />); Monitors.get_default().forEach(m => <Bar monitor={m} />);
Monitors.get_default().forEach(m => <ScreenCorners monitor={m} />);
// Init services // Init services
timeout(1000, () => { timeout(1000, () => {

View file

@ -6,6 +6,12 @@ label.icon {
@include font.icon; @include font.icon;
} }
.screen-corner {
@include lib.rounded(15);
background-color: scheme.$mantle;
}
.notification { .notification {
.inner { .inner {
@include lib.rounded(8); @include lib.rounded(8);

View file

@ -8,6 +8,7 @@ import { bindCurrentTime, osIcon } from "@/utils/system";
import type { AstalWidget } from "@/utils/types"; import type { AstalWidget } from "@/utils/types";
import { setupCustomTooltip } from "@/utils/widgets"; import { setupCustomTooltip } from "@/utils/widgets";
import type PopupWindow from "@/widgets/popupwindow"; import type PopupWindow from "@/widgets/popupwindow";
import ScreenCorner from "@/widgets/screencorner";
import { execAsync, Variable } from "astal"; import { execAsync, Variable } from "astal";
import Binding, { bind, kebabify } from "astal/binding"; import Binding, { bind, kebabify } from "astal/binding";
import { App, Astal, Gtk } from "astal/gtk3"; import { App, Astal, Gtk } from "astal/gtk3";
@ -560,63 +561,79 @@ const bindWidget = (module: keyof typeof config.modules, Widget: () => JSX.Eleme
const bindCompositeWidget = (module: keyof typeof config.modules, binding: Binding<JSX.Element>) => const bindCompositeWidget = (module: keyof typeof config.modules, binding: Binding<JSX.Element>) =>
bind(Variable.derive([config.modules[module].enabled, binding], (e, w) => (e ? w : <Dummy />))); bind(Variable.derive([config.modules[module].enabled, binding], (e, w) => (e ? w : <Dummy />)));
export default ({ monitor }: { monitor: Monitor }) => { const Bar = ({ monitor }: { monitor: Monitor }) => {
const className = Variable.derive( const className = Variable.derive(
[bind(config.vertical), bind(config.style)], [bind(config.vertical), bind(config.style)],
(v, s) => `bar ${v ? "vertical" : " horizontal"} ${s}` (v, s) => `bar ${v ? "vertical" : " horizontal"} ${s}`
); );
return ( return (
<window <centerbox vertical={bind(config.vertical)} className={bind(className)} onDestroy={() => className.drop()}>
namespace="caelestia-bar" <box vertical={bind(config.vertical)}>
monitor={monitor.id} {bindWidget("osIcon", OSIcon)}
anchor={bind(config.vertical).as( {bindWidget("activeWindow", ActiveWindow)}
v => {bindWidget("mediaPlaying", MediaPlaying)}
Astal.WindowAnchor.TOP | <button
Astal.WindowAnchor.LEFT | expand
(v ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT) onScroll={(_, event) =>
)} event.delta_y > 0 ? (monitor.brightness -= 0.1) : (monitor.brightness += 0.1)
exclusivity={Astal.Exclusivity.EXCLUSIVE} }
> />
<centerbox vertical={bind(config.vertical)} className={bind(className)} onDestroy={() => className.drop()}> </box>
<box vertical={bind(config.vertical)}> {bindWidget("workspaces", Workspaces)}
{bindWidget("osIcon", OSIcon)} <box vertical={bind(config.vertical)}>
{bindWidget("activeWindow", ActiveWindow)} <button
{bindWidget("mediaPlaying", MediaPlaying)} expand
<button onScroll={(_, event) => {
expand const speaker = AstalWp.get_default()?.audio.defaultSpeaker;
onScroll={(_, event) => if (!speaker) return console.error("Unable to connect to WirePlumber.");
event.delta_y > 0 ? (monitor.brightness -= 0.1) : (monitor.brightness += 0.1) speaker.mute = false;
} if (event.delta_y > 0) speaker.volume -= 0.1;
/> else speaker.volume += 0.1;
</box> }}
{bindWidget("workspaces", Workspaces)} />
<box vertical={bind(config.vertical)}> {bindWidget("tray", Tray)}
<button {bindWidget("statusIcons", StatusIcons)}
expand {bindWidget("pkgUpdates", PkgUpdates)}
onScroll={(_, event) => { {bindWidget("notifCount", NotifCount)}
const speaker = AstalWp.get_default()?.audio.defaultSpeaker; {bindCompositeWidget(
if (!speaker) return console.error("Unable to connect to WirePlumber."); "battery",
speaker.mute = false; bind(AstalBattery.get_default(), "isBattery").as(b => (b ? <Battery /> : <Dummy />))
if (event.delta_y > 0) speaker.volume -= 0.1; )}
else speaker.volume += 0.1; {bindCompositeWidget(
}} "dateTime",
/> bind(config.vertical).as(v => (v ? <DateTimeVertical /> : <DateTime />))
{bindWidget("tray", Tray)} )}
{bindWidget("statusIcons", StatusIcons)} {bindWidget("power", Power)}
{bindWidget("pkgUpdates", PkgUpdates)} </box>
{bindWidget("notifCount", NotifCount)} </centerbox>
{bindCompositeWidget(
"battery",
bind(AstalBattery.get_default(), "isBattery").as(b => (b ? <Battery /> : <Dummy />))
)}
{bindCompositeWidget(
"dateTime",
bind(config.vertical).as(v => (v ? <DateTimeVertical /> : <DateTime />))
)}
{bindWidget("power", Power)}
</box>
</centerbox>
</window>
); );
}; };
export default ({ monitor }: { monitor: Monitor }) => (
<window
namespace="caelestia-bar"
monitor={monitor.id}
anchor={bind(config.vertical).as(
v =>
Astal.WindowAnchor.TOP |
Astal.WindowAnchor.LEFT |
(v ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT)
)}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
>
<overlay
passThrough
overlays={[
<ScreenCorner place="topleft" />,
<ScreenCorner
halign={bind(config.vertical).as(v => (v ? undefined : Gtk.Align.END))}
valign={bind(config.vertical).as(v => (v ? Gtk.Align.END : undefined))}
place={bind(config.vertical).as(v => (v ? "bottomleft" : "topright"))}
/>,
]}
>
<Bar monitor={monitor} />
</overlay>
</window>
);

View file

@ -0,0 +1,24 @@
import type { Monitor } from "@/services/monitors";
import ScreenCorner from "@/widgets/screencorner";
import { bind } from "astal/binding";
import { Astal } from "astal/gtk3";
import { bar } from "config";
export default ({ monitor }: { monitor: Monitor }) => (
<window
namespace="caelestia-screencorners"
monitor={monitor.id}
anchor={bind(bar.vertical).as(
v =>
Astal.WindowAnchor.BOTTOM |
Astal.WindowAnchor.RIGHT |
(v ? Astal.WindowAnchor.TOP : Astal.WindowAnchor.LEFT)
)}
>
<box vertical={bind(bar.vertical)}>
<ScreenCorner place={bind(bar.vertical).as(v => (v ? "topright" : "bottomleft"))} />
<box expand />
<ScreenCorner place="bottomright" />
</box>
</window>
);

View file

@ -0,0 +1,49 @@
import type { Binding } from "astal";
import { Gtk, type Widget } from "astal/gtk3";
import type cairo from "cairo";
type Place = "topleft" | "topright" | "bottomleft" | "bottomright";
export default ({ place, ...rest }: Widget.DrawingAreaProps & { place: Place | Binding<Place> }) => (
<drawingarea
{...rest}
className="screen-corner"
setup={self => {
self.connect("realize", () => self.get_window()?.set_pass_through(true));
const r = self.get_style_context().get_property("border-radius", Gtk.StateFlags.NORMAL) as number;
self.set_size_request(r, r);
self.connect("draw", (_, cr: cairo.Context) => {
const c = self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL);
const r = self.get_style_context().get_property("border-radius", Gtk.StateFlags.NORMAL) as number;
self.set_size_request(r, r);
switch (typeof place === "string" ? place : place.get()) {
case "topleft":
cr.arc(r, r, r, Math.PI, (3 * Math.PI) / 2);
cr.lineTo(0, 0);
break;
case "topright":
cr.arc(0, r, r, (3 * Math.PI) / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case "bottomleft":
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case "bottomright":
cr.arc(0, 0, r, 0, Math.PI / 2);
cr.lineTo(r, r);
break;
}
cr.closePath();
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
cr.fill();
});
}}
/>
);