ui: single rounding knob, fix balloon-shaped cards

New settings/ui.nix holds `radius` as the one source of truth. Theme
derives radiusCard/Small/Tiny from it and hyprland's decoration.rounding
reads the same value, so one number rescales the whole desktop —
previously the shell and the window corners were unrelated constants,
which is why doubling Theme.radius left windows at 8.

Cards were using the SAME radius as the panel containing them. Nested
corners need outer − gap; matching them makes the inner corner
over-round for its size, which is the inflated/balloon look. All nine
inner surfaces now use radiusCard (radius − panelGap). Invisible at
r=8, obvious at r=32.

Radius 32 -> 24.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-07-28 13:58:28 +01:00
parent a36a3f35b7
commit b2e4e9863b
3 changed files with 42 additions and 13 deletions

View file

@ -3,6 +3,7 @@
let
isMacbook = config.networking.hostName == "FredOS-Macbook";
isGaming = !isMacbook;
ui = import ./ui.nix; # shared corner-rounding knob
in
{
@ -114,7 +115,8 @@ in
};
decoration = {
rounding = 8;
# tracks Theme.radius via settings/ui.nix
rounding = ui.radius;
blur = {
enabled = true;
size = 1;

View file

@ -4,6 +4,7 @@
{ config, pkgs, lib, ... }:
let
isMacbook = config.networking.hostName == "FredOS-Macbook";
ui = import ./ui.nix; # shared corner-rounding knob
in
{
config = lib.mkIf (lib.elem config.networking.hostName [ "FredOS-Gaming" "FredOS-Macbook" ]) {
@ -180,9 +181,19 @@ in
// here, not in ~15 places. Wide enough for the stacked HH/mm
// clock, which is what sets the floor.
readonly property int barWidth: 46
readonly property int radius: 32 // cards, panels, dropdowns
readonly property int radiusSmall: 24 // workspace dots, pill buttons
readonly property int radiusTiny: 16 // hover rows, action chips
// Rounding. ONE knob: settings/ui.nix `radius`. Everything
// below derives from it, and hyprland's window rounding reads
// the same value, so changing that one number rescales the
// roundness of the entire desktop.
readonly property int radius: ${toString ui.radius} // panels, dropdowns, screen cutout
// Concentric, NOT a factor: a card inside a panel needs the
// panel's radius MINUS the gap between them. Give it the same
// radius as its container and the inner corner is over-round
// for its size the card reads as inflated/balloon-like,
// invisible at r=8 and obvious by r=32.
readonly property int radiusCard: radius - panelGap
readonly property int radiusSmall: Math.round(radius * 0.75) // pill buttons, workspace dots
readonly property int radiusTiny: Math.round(radius * 0.5) // hover rows, action chips
readonly property int cardPad: 8 // card inner padding (inset = 2×)
readonly property int panelGap: 8 // gap between panel border and cards (all dropdowns/menus/toast)
@ -311,7 +322,7 @@ in
anchors.horizontalCenter: parent.horizontalCenter
width: 280
height: 44
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
border.width: Theme.borderWidth
border.color: pam.active ? Theme.base0D
@ -744,7 +755,7 @@ in
component Card: Rectangle {
default property alias cardData: _cardCol.data
property alias cardSpacing: _cardCol.spacing
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
implicitHeight: _cardCol.height + 2 * Theme.cardPad
Column {
@ -1110,7 +1121,7 @@ in
Rectangle {
width: parent.width
height: launcherList.contentHeight + 2 * Theme.cardPad
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
visible: launcherList.count > 0
@ -1173,7 +1184,7 @@ in
Rectangle {
width: parent.width
height: 36
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
SIcon {
@ -2201,7 +2212,7 @@ in
anchors.centerIn: parent
width: 48
height: sessionCol.height + 8
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
// Sliding selection pill same tech as the power
@ -2209,7 +2220,7 @@ in
Rectangle {
width: 40
height: 40
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.base02
border.width: 1
border.color: Theme.base03
@ -2984,7 +2995,7 @@ in
Rectangle {
width: parent.width
height: weatherRow.height + 16
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
visible: calPopup.weatherDays.length > 0
@ -3046,7 +3057,7 @@ in
property var pwNode: calPopup.streamFor(modelData)
width: parent.width
height: mediaCol.height + 16
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
PwObjectTracker { objects: mediaCard.pwNode ? [mediaCard.pwNode] : [] }
@ -3497,7 +3508,7 @@ in
anchors.margins: Theme.panelGap
width: 320 - 2 * Theme.panelGap
height: Math.max(toastPreview.visible ? 48 : 0, toastCol.height) + 16
radius: Theme.radius
radius: Theme.radiusCard
color: Theme.cardBg
property string previewSource: bar.notifPreviewSource(toastItem.currentNotif)

16
settings/ui.nix Normal file
View file

@ -0,0 +1,16 @@
# settings/ui.nix — shared UI metrics.
#
# Single source of truth for corner rounding. Both the quickshell shell
# (settings/quickshell.nix -> Theme.qml) and hyprland's window decoration
# (settings/hyprland.nix) read `radius` from here, and every other rounding
# value is derived from it, so this one number rescales the roundness of the
# whole desktop — panels, cards, pills, hover rows and window corners.
#
# Derived values live next to their consumers:
# Theme.radiusCard = radius - panelGap (concentric nesting, see Theme.qml)
# Theme.radiusSmall = radius * 0.75
# Theme.radiusTiny = radius * 0.5
# hyprland decoration.rounding = radius
{
radius = 24;
}