Port AlfheimOS quicksettings styling with scrollable popup
This commit is contained in:
parent
d5255078d0
commit
a9cfd9e5c2
|
|
@ -1,6 +1,22 @@
|
||||||
@use "palette" as *;
|
@use "palette" as *;
|
||||||
|
|
||||||
$radius: 12px;
|
// theme vars ported from AlfheimOS, mapped to the stylix palette
|
||||||
|
$radius: 12px; // keep in sync with RADIUS in Bar.tsx
|
||||||
|
$spacing: 8px;
|
||||||
|
$padding: 8px;
|
||||||
|
$border-width: 1px;
|
||||||
|
$border-color: $base02;
|
||||||
|
$border: $border-width solid $border-color;
|
||||||
|
$transition: 180ms;
|
||||||
|
$fg: $base05;
|
||||||
|
$primary-bg: $base0D;
|
||||||
|
$primary-fg: $base00;
|
||||||
|
$widget-bg: transparentize($base02, 0.45);
|
||||||
|
$hover-bg: transparentize($base02, 0.2);
|
||||||
|
$hover-overlay: transparentize($base05, 0.92);
|
||||||
|
$active-hover: transparentize($primary-bg, 0.15);
|
||||||
|
$active-gradient: linear-gradient(to right, $primary-bg, darken($primary-bg, 4%));
|
||||||
|
|
||||||
$bar-alpha: 0.7;
|
$bar-alpha: 0.7;
|
||||||
$bar-bg: transparentize($base01, 1 - $bar-alpha);
|
$bar-bg: transparentize($base01, 1 - $bar-alpha);
|
||||||
$module-bg: transparentize($base02, 0.4);
|
$module-bg: transparentize($base02, 0.4);
|
||||||
|
|
@ -62,7 +78,7 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
|
|
||||||
.bar .end {
|
.bar .end {
|
||||||
border-radius: ($radius * 0.75) ($radius * 2) ($radius * 0.75) ($radius * 2);
|
border-radius: ($radius * 0.75) ($radius * 2) ($radius * 0.75) ($radius * 2);
|
||||||
margin: 2px;
|
margin: 0 2px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar .last {
|
.bar .last {
|
||||||
|
|
@ -92,8 +108,8 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws.active {
|
.ws.active {
|
||||||
background: $base0D;
|
background: $primary-bg;
|
||||||
color: $base00;
|
color: $primary-fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws:hover {
|
.ws:hover {
|
||||||
|
|
@ -104,20 +120,177 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wifi icon,
|
.quicksettings .notif-count,
|
||||||
.wifi-header icon {
|
.quicksettings .bt-count {
|
||||||
font-size: 16px;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-button {
|
.battery-level {
|
||||||
margin: 4px 0;
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-pill {
|
// ---------- quicksettings popup ----------
|
||||||
padding: 6px 8px;
|
|
||||||
|
.dropdown {
|
||||||
|
background: $base01;
|
||||||
|
border: $border-width solid transparentize($base02, 0.5);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0 2px;
|
||||||
|
|
||||||
|
slider {
|
||||||
|
background: $base03;
|
||||||
|
border-radius: 999px;
|
||||||
|
min-width: 6px;
|
||||||
|
min-height: 6px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: transparentize($fg, 0.15);
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
padding: 7px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: $widget-bg;
|
||||||
|
border: $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 7px 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: transparentize($base05, 0.88);
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-section {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-percent {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-status {
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// volume, microphone and brightness rows on one widget card
|
||||||
|
.sliders-box {
|
||||||
|
background-color: $widget-bg;
|
||||||
|
border: $border;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
.volume,
|
||||||
|
.microphone,
|
||||||
|
.brightness {
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 5px 7px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared slider look: bordered trough, accent gradient highlight,
|
||||||
|
// thumb only appears on hover, .muted turns the highlight gray
|
||||||
|
scale {
|
||||||
|
trough {
|
||||||
|
border-radius: 999px;
|
||||||
|
border: $border;
|
||||||
|
background-color: $base01;
|
||||||
|
min-height: 8px;
|
||||||
|
|
||||||
|
highlight {
|
||||||
|
border-radius: 999px;
|
||||||
|
background-image: $active-gradient;
|
||||||
|
min-height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
slider {
|
||||||
|
background-color: transparent;
|
||||||
|
border: $border-width solid transparent;
|
||||||
|
border-radius: 999px;
|
||||||
|
min-width: 10px;
|
||||||
|
min-height: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
trough {
|
||||||
|
background-color: transparentize($base01, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
trough slider {
|
||||||
|
background-color: $base04;
|
||||||
|
border-color: $border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.muted trough highlight {
|
||||||
|
background-image: none;
|
||||||
|
background-color: transparentize($fg, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// icon + label pill with a detached arrow
|
||||||
|
.toggle-button {
|
||||||
|
margin: 4px 0;
|
||||||
|
background-color: $widget-bg;
|
||||||
|
border: $border;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
button:first-child {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:last-child {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,40 +299,61 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
button:hover {
|
||||||
font-weight: bold;
|
background: $hover-overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active .toggle-pill {
|
&.active {
|
||||||
background: $base0D;
|
background-color: $primary-bg;
|
||||||
color: $base00;
|
|
||||||
|
|
||||||
&:hover {
|
label,
|
||||||
background: transparentize($base0D, 0.2);
|
icon {
|
||||||
|
color: $primary-fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: $active-hover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
// arrow-less toggle pill (do not disturb)
|
||||||
padding: 4px 6px;
|
.simple-toggle {
|
||||||
border-radius: 8px;
|
margin: 4px 0;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: $widget-bg;
|
||||||
|
border: $border;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
icon {
|
.icon {
|
||||||
font-size: 12px;
|
font-size: 16px;
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: transparentize($base05, 0.88);
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $primary-bg;
|
||||||
|
|
||||||
|
label,
|
||||||
|
icon {
|
||||||
|
color: $primary-fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $active-hover;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bluetooth-section {
|
|
||||||
margin-top: 10px;
|
|
||||||
border-top: 1px solid $base02;
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
|
background-color: $widget-bg;
|
||||||
|
border: $border;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px;
|
||||||
margin: 4px 0 8px;
|
margin: 4px 0 8px;
|
||||||
|
|
||||||
.title-box {
|
.title-box {
|
||||||
|
|
@ -167,7 +361,7 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-right: 6px;
|
margin: 0 4px 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|
@ -177,14 +371,78 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-separator {
|
.menu-separator {
|
||||||
background: $base02;
|
background: $border-color;
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 5px 7px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-overlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
background: $base03;
|
||||||
|
border-radius: 999px;
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 16px;
|
||||||
|
margin: 1px 0;
|
||||||
|
|
||||||
|
slider {
|
||||||
|
background: $base05;
|
||||||
|
border-radius: 999px;
|
||||||
|
min-width: 12px;
|
||||||
|
min-height: 12px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch:checked {
|
||||||
|
background: $primary-bg;
|
||||||
|
|
||||||
|
slider {
|
||||||
|
background: $primary-fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-status,
|
||||||
|
.wifi-empty {
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-password {
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: $base01;
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-row {
|
||||||
|
margin: 1px 0;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-row-ssid {
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-item {
|
.device-item {
|
||||||
padding: 4px 6px;
|
padding: 5px 7px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin: 1px 0;
|
margin: 1px 0;
|
||||||
|
|
||||||
|
|
@ -209,95 +467,22 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: transparentize($base05, 0.88);
|
background: $hover-overlay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown {
|
// media player card
|
||||||
background: $base01;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
color: $base05;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
padding: 7px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: transparentize($base02, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.username {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-action {
|
|
||||||
padding: 8px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparentize($base05, 0.88);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.qs-row {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qs-toggle {
|
|
||||||
margin-right: 8px;
|
|
||||||
padding: 0 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparentize($base05, 0.88);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vol-slider {
|
|
||||||
trough {
|
|
||||||
background: $base03;
|
|
||||||
border-radius: 999px;
|
|
||||||
min-height: 12px;
|
|
||||||
|
|
||||||
highlight {
|
|
||||||
background: $base0D;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
slider {
|
|
||||||
min-width: 16px;
|
|
||||||
min-height: 16px;
|
|
||||||
background: $base05;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-section {
|
|
||||||
margin-top: 10px;
|
|
||||||
border-top: 1px solid $base02;
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player {
|
.player {
|
||||||
|
background-color: $widget-bg;
|
||||||
|
border: $border;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
.cover {
|
.cover {
|
||||||
min-width: 48px;
|
|
||||||
min-height: 48px;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
background-color: transparentize($base02, 0.4);
|
background-color: $hover-bg;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
@ -307,171 +492,57 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-artist {
|
.player-artist {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: transparentize($fg, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
scale {
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
trough {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
trough highlight {
|
||||||
|
background-image: linear-gradient($fg, $fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-ctl {
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-overlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-pause {
|
||||||
|
background-color: $primary-bg;
|
||||||
|
|
||||||
|
icon {
|
||||||
|
color: $primary-fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $active-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-position,
|
||||||
|
.player-length {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.position {
|
|
||||||
trough {
|
|
||||||
background: $base03;
|
|
||||||
border-radius: 999px;
|
|
||||||
min-height: 10px;
|
|
||||||
|
|
||||||
highlight {
|
|
||||||
background: $base0D;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
slider {
|
|
||||||
min-width: 12px;
|
|
||||||
min-height: 12px;
|
|
||||||
background: $base05;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-ctl {
|
|
||||||
padding: 4px 6px;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
icon {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparentize($base05, 0.88);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar .media {
|
|
||||||
icon {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-title {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-section {
|
|
||||||
margin-top: 10px;
|
|
||||||
border-top: 1px solid $base02;
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-toggle {
|
|
||||||
padding: 4px 6px;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparentize($base05, 0.88);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-header {
|
|
||||||
label {
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-rescan {
|
|
||||||
padding: 0 2px;
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-header,
|
|
||||||
.device-item {
|
|
||||||
switch {
|
|
||||||
background: $base03;
|
|
||||||
border-radius: 999px;
|
|
||||||
min-width: 34px;
|
|
||||||
min-height: 16px;
|
|
||||||
margin: 1px 0;
|
|
||||||
|
|
||||||
slider {
|
|
||||||
background: $base05;
|
|
||||||
border-radius: 999px;
|
|
||||||
min-width: 12px;
|
|
||||||
min-height: 12px;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch:checked {
|
|
||||||
background: $base0D;
|
|
||||||
|
|
||||||
slider {
|
|
||||||
background: $base00;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-status,
|
|
||||||
.wifi-empty {
|
|
||||||
font-size: 0.85em;
|
|
||||||
opacity: 0.7;
|
|
||||||
margin: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-password {
|
|
||||||
margin: 4px 0;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: $base02;
|
|
||||||
color: $base05;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-row {
|
|
||||||
padding: 4px 6px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 1px 0;
|
|
||||||
|
|
||||||
icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-row-ssid {
|
|
||||||
margin: 0 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-row:hover {
|
|
||||||
background: transparentize($base05, 0.88);
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-level {
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-section {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-percent {
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-status {
|
|
||||||
font-size: 0.85em;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-section {
|
.notif-section {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
border-top: 1px solid $base02;
|
border-top: 1px solid $border-color;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,7 +559,7 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: transparentize($base05, 0.88);
|
background: $hover-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -497,31 +568,19 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quicksettings .notif-count {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quicksettings .bt-count {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-section .notif {
|
|
||||||
background: $base02;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications {
|
.notifications {
|
||||||
.notif {
|
.notif {
|
||||||
background: $base01;
|
background-color: $widget-bg;
|
||||||
|
border: $border;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 8px 12px;
|
padding: 8px 10px;
|
||||||
color: $base05;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: transparentize($base05, 0.88);
|
background: $hover-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.critical {
|
&.critical {
|
||||||
border: 1px solid $base08;
|
border-color: $base08;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,6 +601,10 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
.notif-close {
|
.notif-close {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-overlay;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -561,13 +624,13 @@ $module-bg: transparentize($base02, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notif-action {
|
.notif-action {
|
||||||
background: $base02;
|
background: $base01;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $base03;
|
background: $hover-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ function DeviceItem(device: AstalBluetooth.Device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BluetoothSection({ open }: { open: Variable<boolean> }) {
|
export function BluetoothSection({ open }: { open: Variable<boolean> }) {
|
||||||
if (!bt.adapter) return <box />
|
if (!bt.adapter) return null
|
||||||
|
|
||||||
const expanded = Variable(false)
|
const expanded = Variable(false)
|
||||||
open.subscribe((v) => {
|
open.subscribe((v) => {
|
||||||
|
|
@ -120,63 +120,58 @@ export function BluetoothSection({ open }: { open: Variable<boolean> }) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
const toggle = (
|
||||||
<box className="bluetooth-section" vertical>
|
<ArrowToggleButton
|
||||||
<ArrowToggleButton
|
open={expanded}
|
||||||
open={expanded}
|
icon={btIcon}
|
||||||
icon={btIcon}
|
label={pillLabel()}
|
||||||
label={pillLabel()}
|
active={bind(bt, "is_powered")}
|
||||||
active={bind(bt, "is_powered")}
|
onMainClick={() => {
|
||||||
onMainClick={() => {
|
if (bt.is_powered) {
|
||||||
if (bt.is_powered) {
|
power(false)
|
||||||
power(false)
|
expanded.set(false)
|
||||||
expanded.set(false)
|
setDiscovery(false)
|
||||||
setDiscovery(false)
|
} else {
|
||||||
} else {
|
power(true)
|
||||||
power(true)
|
}
|
||||||
}
|
}}
|
||||||
}}
|
onArrowClick={() => {
|
||||||
onArrowClick={() => {
|
const next = !expanded.get()
|
||||||
const next = !expanded.get()
|
expanded.set(next)
|
||||||
expanded.set(next)
|
if (next) {
|
||||||
if (next) {
|
if (!bt.is_powered) power(true)
|
||||||
if (!bt.is_powered) power(true)
|
setDiscovery(true)
|
||||||
setDiscovery(true)
|
} else {
|
||||||
} else {
|
setDiscovery(false)
|
||||||
setDiscovery(false)
|
}
|
||||||
}
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
<Menu open={expanded} icon="bluetooth-active-symbolic" title="Bluetooth">
|
|
||||||
<label
|
|
||||||
className="wifi-empty"
|
|
||||||
label="bluetooth is off"
|
|
||||||
visible={bind(bt, "is_powered").as((p) => !p)}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
className="wifi-empty"
|
|
||||||
label="searching for devices…"
|
|
||||||
visible={Variable.derive(
|
|
||||||
[bind(bt, "is_powered"), devices],
|
|
||||||
(powered, ds) => powered && ds.filter((d) => d.name).length === 0,
|
|
||||||
)()}
|
|
||||||
/>
|
|
||||||
<scrollable
|
|
||||||
hscroll={Gtk.PolicyType.NEVER}
|
|
||||||
vscroll={Gtk.PolicyType.AUTOMATIC}
|
|
||||||
maxContentHeight={360}
|
|
||||||
propagateNaturalHeight
|
|
||||||
visible={bind(bt, "is_powered")}
|
|
||||||
>
|
|
||||||
<box vertical>
|
|
||||||
{devices().as((ds) =>
|
|
||||||
ds.filter((d) => d.name).map(DeviceItem),
|
|
||||||
)}
|
|
||||||
</box>
|
|
||||||
</scrollable>
|
|
||||||
</Menu>
|
|
||||||
</box>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const menu = (
|
||||||
|
<Menu open={expanded} icon="bluetooth-active-symbolic" title="Bluetooth">
|
||||||
|
<label
|
||||||
|
className="wifi-empty"
|
||||||
|
label="bluetooth is off"
|
||||||
|
visible={bind(bt, "is_powered").as((p) => !p)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="wifi-empty"
|
||||||
|
label="searching for devices…"
|
||||||
|
visible={Variable.derive(
|
||||||
|
[bind(bt, "is_powered"), devices],
|
||||||
|
(powered, ds) => powered && ds.filter((d) => d.name).length === 0,
|
||||||
|
)()}
|
||||||
|
/>
|
||||||
|
<box vertical visible={bind(bt, "is_powered")}>
|
||||||
|
{devices().as((ds) =>
|
||||||
|
ds.filter((d) => d.name).map(DeviceItem),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
|
||||||
|
return { toggle, menu }
|
||||||
}
|
}
|
||||||
|
|
||||||
// bar cluster indicator: shown while bluetooth is powered, with a count
|
// bar cluster indicator: shown while bluetooth is powered, with a count
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,11 @@ const percent = Variable(0).poll(
|
||||||
|
|
||||||
export function BrightnessRow() {
|
export function BrightnessRow() {
|
||||||
return (
|
return (
|
||||||
<box className="qs-row">
|
<box className="brightness" spacing={6}>
|
||||||
<button className="qs-toggle">
|
<button>
|
||||||
<icon icon="display-brightness-symbolic" />
|
<icon icon="display-brightness-symbolic" />
|
||||||
</button>
|
</button>
|
||||||
<slider
|
<slider
|
||||||
className="vol-slider"
|
|
||||||
hexpand
|
hexpand
|
||||||
value={bind(percent)}
|
value={bind(percent)}
|
||||||
onDragged={(self) => {
|
onDragged={(self) => {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ interface DropdownProps {
|
||||||
width?: number
|
width?: number
|
||||||
vertical?: boolean
|
vertical?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
|
// cap the popup height to the screen, scrolling any overflow
|
||||||
|
maxHeight?: number
|
||||||
children: Array<JSX.Element>
|
children: Array<JSX.Element>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,6 +23,7 @@ export default function Dropdown({
|
||||||
width = 260,
|
width = 260,
|
||||||
vertical = true,
|
vertical = true,
|
||||||
className = "",
|
className = "",
|
||||||
|
maxHeight,
|
||||||
children,
|
children,
|
||||||
}: DropdownProps) {
|
}: DropdownProps) {
|
||||||
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor
|
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor
|
||||||
|
|
@ -34,7 +37,19 @@ export default function Dropdown({
|
||||||
halign={Gtk.Align.START}
|
halign={Gtk.Align.START}
|
||||||
valign={Gtk.Align.START}
|
valign={Gtk.Align.START}
|
||||||
>
|
>
|
||||||
{children}
|
{maxHeight !== undefined ? (
|
||||||
|
<scrollable
|
||||||
|
hexpand
|
||||||
|
hscroll={Gtk.PolicyType.NEVER}
|
||||||
|
vscroll={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
maxContentHeight={maxHeight}
|
||||||
|
propagateNaturalHeight
|
||||||
|
>
|
||||||
|
<box vertical>{children}</box>
|
||||||
|
</scrollable>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Gtk from "gi://Gtk?version=3.0"
|
import Gtk from "gi://Gtk?version=3.0"
|
||||||
|
import GLib from "gi://GLib"
|
||||||
import Pango from "gi://Pango"
|
import Pango from "gi://Pango"
|
||||||
import AstalMpris from "gi://AstalMpris"
|
import AstalMpris from "gi://AstalMpris"
|
||||||
import { Variable, bind } from "astal"
|
import { Variable, bind } from "astal"
|
||||||
|
|
@ -25,10 +26,15 @@ function playerIcon(entry: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function PlayerCard(player: AstalMpris.Player) {
|
function PlayerCard(player: AstalMpris.Player) {
|
||||||
// position only changes on the bus when seeking; poll for smooth progress
|
// position only changes on the bus when seeking, so poll every second;
|
||||||
const position = Variable(-1).poll(1000, () =>
|
// the ticker stops itself when the player disconnects so we never
|
||||||
player.available ? player.position : -1,
|
// touch a disposed GObject
|
||||||
)
|
const position = Variable(-1)
|
||||||
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
|
||||||
|
if (!mpris.players.includes(player)) return GLib.SOURCE_REMOVE
|
||||||
|
position.set(player.available ? player.position : -1)
|
||||||
|
return GLib.SOURCE_CONTINUE
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box className="player" spacing={8}>
|
<box className="player" spacing={8}>
|
||||||
|
|
@ -38,10 +44,12 @@ function PlayerCard(player: AstalMpris.Player) {
|
||||||
c !== "" ? `background-image: url('${encodeURI(`file://${c}`)}')` : "",
|
c !== "" ? `background-image: url('${encodeURI(`file://${c}`)}')` : "",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{/* kept allocated (opacity, not visible) so the padded box
|
||||||
|
stays 48px and the icon sits dead center with no art */}
|
||||||
<icon
|
<icon
|
||||||
pixelSize={24}
|
pixelSize={24}
|
||||||
icon={NO_ART}
|
icon={NO_ART}
|
||||||
visible={bind(player, "cover_art").as((c) => c === "")}
|
opacity={bind(player, "cover_art").as((c) => (c !== "" ? 0 : 1))}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
<box vertical spacing={4}>
|
<box vertical spacing={4}>
|
||||||
|
|
@ -69,7 +77,6 @@ function PlayerCard(player: AstalMpris.Player) {
|
||||||
visible={bind(player, "artist").as((a) => (a || "") !== "")}
|
visible={bind(player, "artist").as((a) => (a || "") !== "")}
|
||||||
/>
|
/>
|
||||||
<slider
|
<slider
|
||||||
className="position"
|
|
||||||
value={position().as((p) =>
|
value={position().as((p) =>
|
||||||
player.length > 0
|
player.length > 0
|
||||||
? Math.min(1, Math.max(0, p / player.length))
|
? Math.min(1, Math.max(0, p / player.length))
|
||||||
|
|
@ -94,7 +101,7 @@ function PlayerCard(player: AstalMpris.Player) {
|
||||||
<icon icon={PREV} />
|
<icon icon={PREV} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="player-ctl"
|
className="player-ctl play-pause"
|
||||||
onClicked={() => player.play_pause()}
|
onClicked={() => player.play_pause()}
|
||||||
visible={bind(player, "can_play")}
|
visible={bind(player, "can_play")}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,17 @@ export const dndIcon = Variable.derive([bind(notifd, "dontDisturb")], (dnd) =>
|
||||||
|
|
||||||
export function DndToggle() {
|
export function DndToggle() {
|
||||||
return (
|
return (
|
||||||
<box
|
<button
|
||||||
className={bind(notifd, "dontDisturb").as((v) =>
|
className={bind(notifd, "dontDisturb").as((v) =>
|
||||||
v ? "toggle-button active" : "toggle-button",
|
v ? "simple-toggle active" : "simple-toggle",
|
||||||
)}
|
)}
|
||||||
|
onClicked={() => (notifd.dontDisturb = !notifd.dontDisturb)}
|
||||||
>
|
>
|
||||||
<button
|
<box spacing={8}>
|
||||||
className="toggle-pill"
|
<icon className="icon" icon={dndIcon()} />
|
||||||
hexpand
|
<label label="Do Not Disturb" />
|
||||||
onClicked={() => (notifd.dontDisturb = !notifd.dontDisturb)}
|
</box>
|
||||||
>
|
</button>
|
||||||
<box hexpand>
|
|
||||||
<icon className="icon" icon={dndIcon()} />
|
|
||||||
<label className="label" label="Do Not Disturb" />
|
|
||||||
</box>
|
|
||||||
</button>
|
|
||||||
</box>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,7 @@ function Header() {
|
||||||
<label className="username" label={user} valign={Gtk.Align.CENTER} />
|
<label className="username" label={user} valign={Gtk.Align.CENTER} />
|
||||||
<box hexpand />
|
<box hexpand />
|
||||||
{actions.map((action) => (
|
{actions.map((action) => (
|
||||||
<button
|
<button onClicked={() => action.cmd && execAsync(action.cmd)}>
|
||||||
className="power-action"
|
|
||||||
onClicked={() => action.cmd && execAsync(action.cmd)}
|
|
||||||
>
|
|
||||||
<icon icon={action.icon} />
|
<icon icon={action.icon} />
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
@ -69,6 +66,12 @@ function Header() {
|
||||||
export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
|
export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
|
||||||
const open = Variable(false)
|
const open = Variable(false)
|
||||||
|
|
||||||
|
const wifiSection = WifiSection({ open })
|
||||||
|
const btSection = BluetoothSection({ open })
|
||||||
|
const toggles = [wifiSection?.toggle, btSection?.toggle].filter(
|
||||||
|
(t) => t !== undefined,
|
||||||
|
)
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<button
|
<button
|
||||||
className="quicksettings end"
|
className="quicksettings end"
|
||||||
|
|
@ -105,43 +108,50 @@ export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
|
||||||
)
|
)
|
||||||
|
|
||||||
const popup = (
|
const popup = (
|
||||||
<Dropdown gdkmonitor={gdkmonitor} target={button} open={open} width={280}>
|
<Dropdown
|
||||||
|
gdkmonitor={gdkmonitor}
|
||||||
|
target={button}
|
||||||
|
open={open}
|
||||||
|
width={280}
|
||||||
|
maxHeight={gdkmonitor.get_geometry().height - 200}
|
||||||
|
>
|
||||||
<Header />
|
<Header />
|
||||||
<BatterySection />
|
<BatterySection />
|
||||||
<box className="qs-row">
|
<box className="sliders-box" vertical spacing={2}>
|
||||||
<button
|
<box className="volume" spacing={6}>
|
||||||
className="qs-toggle"
|
<button onClicked={() => (speaker.mute = !speaker.mute)}>
|
||||||
onClicked={() => (speaker.mute = !speaker.mute)}
|
<icon icon={speakerIcon()} />
|
||||||
>
|
|
||||||
<icon icon={speakerIcon()} />
|
|
||||||
</button>
|
|
||||||
<slider
|
|
||||||
className="vol-slider"
|
|
||||||
hexpand
|
|
||||||
value={bind(speaker, "volume")}
|
|
||||||
onDragged={(self) => (speaker.volume = self.value)}
|
|
||||||
/>
|
|
||||||
</box>
|
|
||||||
{mic && micIcon && (
|
|
||||||
<box className="qs-row">
|
|
||||||
<button
|
|
||||||
className="qs-toggle"
|
|
||||||
onClicked={() => (mic.mute = !mic.mute)}
|
|
||||||
>
|
|
||||||
<icon icon={micIcon()} />
|
|
||||||
</button>
|
</button>
|
||||||
<slider
|
<slider
|
||||||
className="vol-slider"
|
|
||||||
hexpand
|
hexpand
|
||||||
value={bind(mic, "volume")}
|
className={bind(speaker, "mute").as((m) => (m ? "muted" : ""))}
|
||||||
onDragged={(self) => (mic.volume = self.value)}
|
value={bind(speaker, "volume")}
|
||||||
|
onDragged={(self) => (speaker.volume = self.value)}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
)}
|
{mic && micIcon && (
|
||||||
<BrightnessRow />
|
<box className="microphone" spacing={6}>
|
||||||
|
<button onClicked={() => (mic.mute = !mic.mute)}>
|
||||||
|
<icon icon={micIcon()} />
|
||||||
|
</button>
|
||||||
|
<slider
|
||||||
|
hexpand
|
||||||
|
className={bind(mic, "mute").as((m) => (m ? "muted" : ""))}
|
||||||
|
value={bind(mic, "volume")}
|
||||||
|
onDragged={(self) => (mic.volume = self.value)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
<BrightnessRow />
|
||||||
|
</box>
|
||||||
<DndToggle />
|
<DndToggle />
|
||||||
<WifiSection open={open} />
|
{toggles.length > 0 && (
|
||||||
<BluetoothSection open={open} />
|
<box homogeneous spacing={6}>
|
||||||
|
{toggles}
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
{wifiSection?.menu}
|
||||||
|
{btSection?.menu}
|
||||||
<MediaSection />
|
<MediaSection />
|
||||||
<box className="notif-section" vertical spacing={6}>
|
<box className="notif-section" vertical spacing={6}>
|
||||||
<box className="notif-header-row" spacing={6}>
|
<box className="notif-header-row" spacing={6}>
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ export function ArrowToggleButton({
|
||||||
return (
|
return (
|
||||||
<box
|
<box
|
||||||
className={active.as((v) => (v ? "toggle-button active" : "toggle-button"))}
|
className={active.as((v) => (v ? "toggle-button active" : "toggle-button"))}
|
||||||
|
hexpand
|
||||||
>
|
>
|
||||||
<button className="toggle-pill" hexpand onClicked={onMainClick}>
|
<button className="toggle-pill" hexpand onClicked={onMainClick}>
|
||||||
<box hexpand>
|
<box hexpand>
|
||||||
|
|
@ -85,7 +86,7 @@ export function ArrowToggleButton({
|
||||||
|
|
||||||
type MenuProps = {
|
type MenuProps = {
|
||||||
open: Variable<boolean>
|
open: Variable<boolean>
|
||||||
icon: string
|
icon: string | Binding<string>
|
||||||
title: string
|
title: string
|
||||||
children: Array<JSX.Element>
|
children: Array<JSX.Element>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import Gtk from "gi://Gtk?version=3.0"
|
import Gtk from "gi://Gtk?version=3.0"
|
||||||
import AstalNetwork from "gi://AstalNetwork"
|
import AstalNetwork from "gi://AstalNetwork"
|
||||||
import { type Binding, type Variable, Variable, bind } from "astal"
|
import { type Binding, type Variable, Variable, bind } from "astal"
|
||||||
|
import { ArrowToggleButton, Menu } from "./ToggleButton"
|
||||||
|
|
||||||
const CONNECTING = [40, 50, 60, 70, 80, 90]
|
const CONNECTING = [40, 50, 60, 70, 80, 90]
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ function WifiRow(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WifiSection({ open }: { open: Variable<boolean> }) {
|
export function WifiSection({ open }: { open: Variable<boolean> }) {
|
||||||
if (!wifi) return <box />
|
if (!wifi) return null
|
||||||
|
|
||||||
const expanded = Variable(false)
|
const expanded = Variable(false)
|
||||||
const pendingAP = Variable<AstalNetwork.AccessPoint | null>(null)
|
const pendingAP = Variable<AstalNetwork.AccessPoint | null>(null)
|
||||||
|
|
@ -111,68 +112,49 @@ export function WifiSection({ open }: { open: Variable<boolean> }) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
const toggle = (
|
||||||
<box className="wifi-section" vertical>
|
<ArrowToggleButton
|
||||||
<box className="wifi-header">
|
open={expanded}
|
||||||
<button
|
icon={bind(wifi, "icon-name")}
|
||||||
className="wifi-toggle"
|
label={bind(wifi, "ssid").as((ssid) => ssid || "Not Connected")}
|
||||||
onClicked={() => expanded.set(!expanded.get())}
|
active={bind(wifi, "enabled")}
|
||||||
>
|
onMainClick={() => {
|
||||||
<box spacing={6}>
|
wifi.enabled = !wifi.enabled
|
||||||
<icon icon={bind(wifi, "icon-name")} />
|
if (wifi.enabled) wifi.scan()
|
||||||
<label label="Wi-Fi" />
|
}}
|
||||||
<icon
|
onArrowClick={() => expanded.set(!expanded.get())}
|
||||||
icon={expanded().as((v) =>
|
/>
|
||||||
v ? "pan-down-symbolic" : "pan-end-symbolic",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</box>
|
|
||||||
</button>
|
|
||||||
<button className="wifi-rescan" onClicked={() => wifi.scan()}>
|
|
||||||
<icon icon="view-refresh-symbolic" />
|
|
||||||
</button>
|
|
||||||
<switch
|
|
||||||
active={bind(wifi, "enabled")}
|
|
||||||
onStateSet={(_, state) => {
|
|
||||||
wifi.enabled = state
|
|
||||||
return true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</box>
|
|
||||||
<revealer
|
|
||||||
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
|
|
||||||
transitionDuration={180}
|
|
||||||
revealChild={bind(expanded)}
|
|
||||||
>
|
|
||||||
<box vertical>
|
|
||||||
<label
|
|
||||||
className="wifi-status"
|
|
||||||
label={status()}
|
|
||||||
visible={status().as((text) => text !== "")}
|
|
||||||
/>
|
|
||||||
{passwordEntry}
|
|
||||||
<label
|
|
||||||
className="wifi-empty"
|
|
||||||
label="wi-fi is off"
|
|
||||||
visible={bind(wifi, "enabled").as((enabled) => !enabled)}
|
|
||||||
/>
|
|
||||||
<scrollable
|
|
||||||
hscroll={Gtk.PolicyType.NEVER}
|
|
||||||
vscroll={Gtk.PolicyType.AUTOMATIC}
|
|
||||||
maxContentHeight={360}
|
|
||||||
propagateNaturalHeight
|
|
||||||
visible={bind(wifi, "enabled")}
|
|
||||||
>
|
|
||||||
<box vertical>
|
|
||||||
{bind(wifi, "access-points").as((aps) =>
|
|
||||||
strongest(aps).map((ap) =>
|
|
||||||
WifiRow(ap, activeSsid, connect)
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</box>
|
|
||||||
</scrollable>
|
|
||||||
</box>
|
|
||||||
</revealer>
|
|
||||||
</box>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const menu = (
|
||||||
|
<Menu open={expanded} icon={bind(wifi, "icon-name")} title="Wi-Fi">
|
||||||
|
<label
|
||||||
|
className="wifi-status"
|
||||||
|
label={status()}
|
||||||
|
visible={status().as((text) => text !== "")}
|
||||||
|
/>
|
||||||
|
{passwordEntry}
|
||||||
|
<label
|
||||||
|
className="wifi-empty"
|
||||||
|
label="wi-fi is off"
|
||||||
|
visible={bind(wifi, "enabled").as((enabled) => !enabled)}
|
||||||
|
/>
|
||||||
|
<box vertical visible={bind(wifi, "enabled")}>
|
||||||
|
{bind(wifi, "access-points").as((aps) =>
|
||||||
|
strongest(aps).map((ap) =>
|
||||||
|
WifiRow(ap, activeSsid, connect)
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
<box className="menu-separator" />
|
||||||
|
<button className="wifi-rescan" onClicked={() => wifi.scan()}>
|
||||||
|
<box spacing={6}>
|
||||||
|
<icon icon="view-refresh-symbolic" />
|
||||||
|
<label label="Rescan" />
|
||||||
|
</box>
|
||||||
|
</button>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
|
||||||
|
return { toggle, menu }
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue