Measured the gap first: of the 38 real Hyprland options Panama's own Lua
sets, only 16 were editable in Settings. Everything else required a text
editor, which is the thing this app exists to stop. This closes most of
that: 66 mapped options now, from 47.
Window shape and shadows on Appearance: corner shape (rounding_power),
focused and fullscreen opacity, shadow falloff and hard-edged shadows.
Window edges, master layout and Hyprland's own notices on Desktop & Dock.
Three of these are corrections rather than additions.
Master layout options existed nowhere, while Settings has offered "Master
and stack" as a choice since this morning -- a layout you can select and
cannot configure is barely a choice. Its card is hidden unless that
layout is actually selected, since settings that do nothing under the
layout you are running are worse than not offering the layout at all.
The four Hyprland notices -- logo, splash, update news, donation nag --
are all turned off by looks.lua on the user's behalf. Defensible as a
default, but not a decision anyone could reverse. They are stored
positively ("show this") and written as Hyprland's `disable_*` through a
new `invert` flag, because a switch labelled "Disable splash text" that
must be ON to hide something is a small cruelty. The Lua does the same
inversion so both sides agree.
Everything new also reads from prefs in looks.lua. Without that these
would apply live and silently revert on the next compositor reload,
which is the failure this codebase keeps designing against.
Two shapes the write path had never seen. Border colours are gradients
and shadow offsets are vec2, and the verifier understood neither -- it
returned false for anything outside int/bool/float/str/css, so both
would have reported every write as rejected. Gradients also need real
care: the stubs declare them as `string|{colors,angle}`, and the string
form carries only ONE stop, so writing "rgba(a) rgba(b) 45deg" as a
string is accepted and keeps the previous value. Verified that directly.
They are also written in one notation and read back in another
(`{colors={"rgba(3b426199)"},angle=45}` becomes `993b4261 45deg`), so
comparison normalises both sides.
Border COLOUR is deliberately not exposed yet. col.inactive_border is
written by ColorScheme on every scheme change, so a user's choice would
be silently overwritten, and col.active_border is the Prism gradient,
which needs a colour control this app does not have. Shadow offset is
left out for the same reason -- the vec2 support is in place for
whenever the widget exists.
Verified each new option applies and reverts against the live
compositor, and that the schema, enum-map, nav, write and commit/reset
contracts all pass.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
174 lines
6.4 KiB
QML
174 lines
6.4 KiB
QML
// Desktop & Dock.
|
|
//
|
|
// The dock timings used to be shown here as text -- "Instant", "250 ms" -- even
|
|
// though they were already stored, mutable integers. They are controls now.
|
|
// The window-layout card keeps text rows because those really are facts about
|
|
// how Panama tiles rather than settings: the adjustable parts of window
|
|
// appearance live on the Appearance page, next to the preview that explains
|
|
// them.
|
|
|
|
import QtQuick
|
|
import qs.config
|
|
import qs.services
|
|
|
|
SettingsPage {
|
|
id: root
|
|
|
|
title: "Desktop & Dock"
|
|
lede: "Keep the shell instant, spatial, and out of your way."
|
|
|
|
SettingsCard {
|
|
title: "Dock"
|
|
|
|
ToggleRow { setting: "dockAutohide" }
|
|
SliderRow { setting: "dockRevealDelayMs"; zeroLabel: "Instant" }
|
|
SliderRow { setting: "dockHideDelayMs"; zeroLabel: "Instant"; divider: false }
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Pinned applications"
|
|
subtitle: "What sits in the Dock whether or not it is running. Order here is the order on screen."
|
|
|
|
DockPinsEditor {
|
|
id: pins
|
|
width: parent.width
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Pin another application"
|
|
|
|
DockAppPicker {
|
|
width: parent.width
|
|
pinned: pins.pinned
|
|
onPicked: id => pins.add(id)
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Window layout"
|
|
subtitle: "Follows the Forge mental model, with native Hyprland tiling."
|
|
|
|
// These were two read-only rows reporting "Tiling" and "Dynamic", which
|
|
// described settings rather than facts -- both are ordinary Hyprland
|
|
// options that simply had no controls. TextRow's own documentation says
|
|
// a setting the user could reasonably change does not belong in it.
|
|
ChoiceRow { setting: "windowLayout" }
|
|
ToggleRow { setting: "preserveSplit" }
|
|
ChoiceRow { setting: "forceSplit" }
|
|
ToggleRow { setting: "windowSnapping" }
|
|
ActionRow {
|
|
label: "Gaps, corners, and effects"
|
|
detail: "Adjusted on the Appearance page, beside a live preview"
|
|
action: "Open Appearance"
|
|
divider: false
|
|
onTriggered: ShellState.openSettings("appearance")
|
|
}
|
|
}
|
|
|
|
// GNOME's Multitasking panel, in Hyprland's terms.
|
|
// Only meaningful when the layout above is Master and stack. Hidden
|
|
// otherwise, because a card of settings that do nothing under the layout
|
|
// you are actually running is worse than not offering the layout at all.
|
|
SettingsCard {
|
|
visible: DesktopPreferences.get("windowLayout") === "master"
|
|
title: "Master and stack"
|
|
subtitle: "How the master area behaves. These apply only while the tiling layout above is Master and stack."
|
|
|
|
SliderRow { setting: "masterFactor" }
|
|
ChoiceRow { setting: "masterOrientation" }
|
|
ChoiceRow { setting: "masterNewStatus" }
|
|
ToggleRow { setting: "masterNewOnTop"; divider: false }
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Window edges"
|
|
subtitle: "How the pointer grabs a window's border, and how floating windows behave near each other and the screen edge."
|
|
|
|
ToggleRow { setting: "resizeOnBorder" }
|
|
SliderRow { setting: "borderGrabArea"; zeroLabel: "Border only" }
|
|
ToggleRow { setting: "hoverIconOnBorder" }
|
|
SliderRow { setting: "snapWindowGap"; zeroLabel: "Touching" }
|
|
SliderRow { setting: "snapMonitorGap"; zeroLabel: "Touching" }
|
|
ToggleRow { setting: "snapRespectGaps"; divider: false }
|
|
}
|
|
|
|
// Hyprland's own interruptions. Panama turns all four off, which is a
|
|
// defensible default and was not previously a decision anyone could
|
|
// reverse without editing looks.lua.
|
|
SettingsCard {
|
|
title: "Hyprland notices"
|
|
subtitle: "Panama hides all of these by default. They are the compositor's own, not Panama's."
|
|
|
|
ToggleRow { setting: "hyprlandLogo" }
|
|
ToggleRow { setting: "hyprlandSplash" }
|
|
ToggleRow { setting: "hyprlandUpdateNews" }
|
|
ToggleRow { setting: "hyprlandDonationNag"; divider: false }
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Workspaces & focus"
|
|
subtitle: "Hyprland's workspaces are created and destroyed as you use them, so there is no fixed count to set."
|
|
|
|
ToggleRow { setting: "workspaceBackAndForth" }
|
|
ToggleRow { setting: "allowWorkspaceCycles" }
|
|
ToggleRow { setting: "focusOnActivate" }
|
|
ToggleRow { setting: "mouseMoveFocusesMonitor" }
|
|
ChoiceRow { setting: "followMouse"; divider: false }
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Focus"
|
|
|
|
SliderRow { setting: "focusDurationMinutes"; divider: false }
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Snapshots"
|
|
subtitle: SettingsBackup.lastError !== ""
|
|
? SettingsBackup.lastError
|
|
: "Your whole desktop configuration is one file, so a snapshot is a copy of it. Restoring also snapshots what it replaces, so it is itself undoable."
|
|
|
|
ActionRow {
|
|
label: "Back up current settings"
|
|
detail: SettingsBackup.snapshots.length === 0
|
|
? "No snapshots yet"
|
|
: SettingsBackup.snapshots.length + (SettingsBackup.snapshots.length === 1 ? " snapshot kept" : " snapshots kept") + ", newest first"
|
|
action: "Back up now"
|
|
enabled: !SettingsBackup.busy
|
|
divider: SettingsBackup.snapshots.length > 0
|
|
onTriggered: SettingsBackup.save()
|
|
}
|
|
|
|
Repeater {
|
|
id: snapshotRows
|
|
model: SettingsBackup.snapshots
|
|
|
|
ActionRow {
|
|
required property var modelData
|
|
required property int index
|
|
|
|
label: modelData.when
|
|
detail: modelData.keys + " settings"
|
|
action: "Restore"
|
|
enabled: !SettingsBackup.busy
|
|
divider: index < snapshotRows.count - 1
|
|
onTriggered: SettingsBackup.restore(modelData.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Reset"
|
|
subtitle: "Restores the appearance, dock, clock, focus, and display policy, and clears your Home accessory arrangement. Pinned applications, files, and paired devices are not changed."
|
|
|
|
ActionRow {
|
|
label: "Restore defaults"
|
|
detail: "Applies immediately, including to the compositor"
|
|
action: "Restore defaults"
|
|
divider: false
|
|
onTriggered: SystemSettings.restoreDefaults()
|
|
}
|
|
}
|
|
}
|