Merge pull request #1517 from caelestia-dots/unstable

Merge unstable into main
This commit is contained in:
2 * r + 2 * t 2026-06-07 22:58:41 +10:00 committed by GitHub
commit e65e918a9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
365 changed files with 16203 additions and 17799 deletions

View file

@ -18,6 +18,7 @@ jobs:
- name: Check QML format - name: Check QML format
shell: fish {0} shell: fish {0}
run: | run: |
/usr/lib/qt6/bin/qmlformat --version
for file in (string match -v 'build/*' **.qml) for file in (string match -v 'build/*' **.qml)
/usr/lib/qt6/bin/qmlformat $file | diff -u $file - || exit 1 /usr/lib/qt6/bin/qmlformat $file | diff -u $file - || exit 1
end end
@ -34,5 +35,6 @@ jobs:
- name: Check C++ format - name: Check C++ format
shell: fish {0} shell: fish {0}
run: | run: |
clang-format --version
find plugin extras -name '*.cpp' -o -name '*.hpp' \ find plugin extras -name '*.cpp' -o -name '*.hpp' \
| xargs clang-format --dry-run --Werror | xargs clang-format --dry-run --Werror

View file

@ -25,6 +25,8 @@ jobs:
- name: Lint QML - name: Lint QML
shell: fish {0} shell: fish {0}
run: | run: |
/usr/lib/qt6/bin/qmllint --version
# Generate tooling # Generate tooling
touch .qmlls.ini touch .qmlls.ini
QT_QPA_PLATFORM=offscreen QML2_IMPORT_PATH="$PWD/build/qml:$QML2_IMPORT_PATH" timeout 2 qs -p . QT_QPA_PLATFORM=offscreen QML2_IMPORT_PATH="$PWD/build/qml:$QML2_IMPORT_PATH" timeout 2 qs -p .

View file

@ -23,7 +23,13 @@ jobs:
run: | run: |
cat > /tmp/Dockerfile <<EOF cat > /tmp/Dockerfile <<EOF
FROM archlinux:latest FROM archlinux:latest
RUN pacman -Syu --needed --noconfirm sudo base-devel cmake ninja fish git clazy qt6-declarative qt6-shadertools python libpipewire aubio libqalculate && \ # TEMPORARY (remove once the upstream qmlformat regression is fixed): pin to the
# 2026-05-13 Arch archive snapshot to hold qmlformat at qt6-declarative 6.11.0.
# qt6-declarative 6.11.1 silently crashes qmlformat (exit 1, empty output) on QML files
# that declare a property named 'id'. To unpin: delete the mirrorlist line below and
# change 'pacman -Syyuu' back to 'pacman -Syu'.
RUN echo 'Server = https://archive.archlinux.org/repos/2026/05/13/\$repo/os/\$arch' > /etc/pacman.d/mirrorlist && \
pacman -Syyuu --needed --noconfirm sudo base-devel cmake ninja fish git clazy qt6-declarative qt6-shadertools python libpipewire aubio libqalculate && \
useradd -m builder && \ useradd -m builder && \
echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
sudo -u builder git clone https://aur.archlinux.org/yay-bin.git /home/builder/yay-bin && \ sudo -u builder git clone https://aur.archlinux.org/yay-bin.git /home/builder/yay-bin && \

View file

@ -71,3 +71,20 @@ if("shell" IN_LIST ENABLE_MODULES)
install(FILES LICENSE DESTINATION "${INSTALL_QSCONFDIR}") install(FILES LICENSE DESTINATION "${INSTALL_QSCONFDIR}")
endif() endif()
if("plugin" IN_LIST ENABLE_MODULES OR "shell" IN_LIST ENABLE_MODULES)
message(STATUS "Fetching M3Shapes module")
include(FetchContent)
FetchContent_Declare(
m3shapes_external
GIT_REPOSITORY https://github.com/soramanew/m3shapes.git
GIT_TAG main
)
FetchContent_MakeAvailable(m3shapes_external)
message(STATUS "Done fetching M3Shapes module")
# Fix m3shapes wrong rpath
if(TARGET m3shapesplugin)
set_target_properties(m3shapesplugin PROPERTIES INSTALL_RPATH "$ORIGIN")
endif()
endif()

387
README.md
View file

@ -240,12 +240,14 @@ For example, to disable the bar on DP-1:
> - `launcher` (`specialPrefix`, `actionPrefix`, `enableDangerousActions`, `vimKeybinds`, > - `launcher` (`specialPrefix`, `actionPrefix`, `enableDangerousActions`, `vimKeybinds`,
> `favouriteApps`, `hiddenApps`, `actions`) > `favouriteApps`, `hiddenApps`, `actions`)
> - `launcher.useFuzzy` (`apps`, `actions`, `schemes`, `variants`, `wallpapers`) > - `launcher.useFuzzy` (`apps`, `actions`, `schemes`, `variants`, `wallpapers`)
> - `notifs` (`expire`, `fullscreen`, `defaultExpireTimeout`, `actionOnClick`) > - `notifs` (`expire`, `fullscreen`, `defaultExpireTimeout`, `fullscreenExpireTimeout`, `actionOnClick`)
> - `lock` (`enableFprint`, `maxFprintTries`) > - `lock` (`enableFprint`, `maxFprintTries`)
> - `utilities` (`toasts`, `vpn`) > - `nexus` (`networkRescanInterval`)
> - `utilities.toasts` (all except `fullscreen`)
> - `utilities.vpn` (`enabled`, `provider`)
> - `services` (`weatherLocation`, `useFahrenheit`, `useFahrenheitPerformance`, `useTwelveHourClock`, > - `services` (`weatherLocation`, `useFahrenheit`, `useFahrenheitPerformance`, `useTwelveHourClock`,
> `gpuType`, `visualiserBars`, `audioIncrement`, `brightnessIncrement`, `maxVolume`, `smartScheme`, > `gpuType`, `visualiserBars`, `audioIncrement`, `brightnessIncrement`, `maxVolume`, `smartScheme`,
> `defaultPlayer`, `playerAliases`, `showLyrics`, `lyricsBackend`) > `defaultPlayer`, `playerAliases`, `lyricsBackend`)
> - `paths` (`wallpaperDir`, `lyricsDir`) > - `paths` (`wallpaperDir`, `lyricsDir`)
> >
> </details> > </details>
@ -265,31 +267,61 @@ For example, to disable the bar on DP-1:
"enabled": true, "enabled": true,
"appearance": { "appearance": {
"deformScale": 1, "deformScale": 1,
"anim": {
"durations": {
"scale": 1
}
},
"font": {
"family": {
"clock": "Rubik",
"material": "Material Symbols Rounded",
"mono": "CaskaydiaCove NF",
"sans": "Rubik"
},
"size": {
"scale": 1
}
},
"padding": {
"scale": 1
},
"rounding": { "rounding": {
"scale": 1 "scale": 1
}, },
"spacing": { "spacing": {
"scale": 1 "scale": 1
}, },
"padding": {
"scale": 1
},
"font": {
"scale": 1,
"clock": "Rubik",
"headline": {
"family": "GoogleSansFlex",
"large": { "size": 32, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } },
"medium": { "size": 28, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } },
"small": { "size": 24, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } }
},
"title": {
"family": "GoogleSansFlex",
"large": { "size": 22, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } },
"medium": { "size": 16, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } },
"small": { "size": 14, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } }
},
"body": {
"family": "GoogleSansFlex",
"large": { "size": 16, "weight": 400, "italic": false, "vaxes": { "ROND": 25 } },
"medium": { "size": 14, "weight": 400, "italic": false, "vaxes": { "ROND": 25 } },
"small": { "size": 12, "weight": 400, "italic": false, "vaxes": { "ROND": 25 } }
},
"label": {
"family": "GoogleSansFlex",
"large": { "size": 14, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } },
"medium": { "size": 12, "weight": 500, "italic": false, "vaxes": { "ROND": 25 } },
"small": { "size": 11, "weight": 400, "italic": false, "vaxes": { "ROND": 25 } }
},
"mono": {
"family": "CaskaydiaCove NF",
"large": { "size": 16, "weight": 400, "italic": false, "vaxes": {} },
"medium": { "size": 14, "weight": 400, "italic": false, "vaxes": {} },
"small": { "size": 12, "weight": 400, "italic": false, "vaxes": {} }
},
"icon": {
"family": "Material Symbols Rounded",
"extraLarge": { "size": 36, "weight": 400, "italic": false, "vaxes": {} },
"large": { "size": 24, "weight": 400, "italic": false, "vaxes": {} },
"medium": { "size": 18, "weight": 400, "italic": false, "vaxes": {} },
"small": { "size": 15, "weight": 400, "italic": false, "vaxes": {} }
}
},
"anim": {
"durations": {
"scale": 1
}
},
"transparency": { "transparency": {
"enabled": false, "enabled": false,
"base": 0.85, "base": 0.85,
@ -297,7 +329,7 @@ For example, to disable the bar on DP-1:
} }
}, },
"general": { "general": {
"logo": "caelestia", "logo": "",
"showOverFullscreen": false, "showOverFullscreen": false,
"mediaGifSpeedAdjustment": 300, "mediaGifSpeedAdjustment": 300,
"sessionGifSpeed": 0.7, "sessionGifSpeed": 0.7,
@ -307,6 +339,25 @@ For example, to disable the bar on DP-1:
"playback": ["mpv"], "playback": ["mpv"],
"explorer": ["thunar"] "explorer": ["thunar"]
}, },
"idle": {
"lockBeforeSleep": true,
"inhibitWhenAudio": true,
"timeouts": [
{
"timeout": 180,
"idleAction": "lock"
},
{
"timeout": 300,
"idleAction": "dpms off",
"returnAction": "dpms on"
},
{
"timeout": 600,
"idleAction": ["systemctl", "suspend-then-hibernate"]
}
]
},
"battery": { "battery": {
"warnLevels": [ "warnLevels": [
{ {
@ -330,65 +381,102 @@ For example, to disable the bar on DP-1:
} }
], ],
"criticalLevel": 3 "criticalLevel": 3
},
"idle": {
"lockBeforeSleep": true,
"inhibitWhenAudio": true,
"timeouts": [
{
"timeout": 180,
"idleAction": "lock"
},
{
"timeout": 300,
"idleAction": "dpms off",
"returnAction": "dpms on"
},
{
"timeout": 600,
"idleAction": ["systemctl", "suspend-then-hibernate"]
}
]
} }
}, },
"background": { "background": {
"enabled": true,
"wallpaperEnabled": true,
"desktopClock": { "desktopClock": {
"enabled": false, "enabled": false,
"scale": 1.0, "scale": 1.0,
"position": "bottom-right", "position": "bottom-right",
"shadow": { "invertColors": false,
"enabled": true,
"opacity": 0.7,
"blur": 0.4
},
"background": { "background": {
"enabled": false, "enabled": false,
"opacity": 0.7, "opacity": 0.7,
"blur": true "blur": true
}, },
"invertColors": false "shadow": {
},
"enabled": true, "enabled": true,
"opacity": 0.7,
"blur": 0.4
}
},
"visualiser": { "visualiser": {
"blur": false,
"enabled": false, "enabled": false,
"autoHide": true, "autoHide": true,
"blur": false,
"rounding": 1, "rounding": 1,
"spacing": 1 "spacing": 1
} }
}, },
"bar": { "bar": {
"persistent": true,
"showOnHover": true,
"dragThreshold": 20,
"scrollActions": {
"workspaces": true,
"volume": true,
"brightness": true
},
"popouts": {
"activeWindow": true,
"tray": true,
"statusIcons": true
},
"workspaces": {
"shown": 5,
"activeIndicator": true,
"occupiedBg": false,
"showWindows": true,
"showWindowsOnSpecialWorkspaces": true,
"maxWindowIcons": 5,
"activeTrail": false,
"perMonitorWorkspaces": true,
"label": " ",
"occupiedLabel": "󰮯",
"activeLabel": "󰮯",
"capitalisation": "preserve",
"specialWorkspaceIcons": [
{
"name": "steam",
"icon": "sports_esports"
}
],
"windowIcons": [
{
"regex": "steam(_app_(default|[0-9]+))?",
"icon": "sports_esports"
}
]
},
"activeWindow": { "activeWindow": {
"compact": false, "compact": false,
"inverted": false, "inverted": false,
"showOnHover": true "showOnHover": true
}, },
"tray": {
"background": false,
"recolour": false,
"compact": false,
"iconSubs": [],
"hiddenIcons": []
},
"status": {
"showAudio": false,
"showMicrophone": false,
"showKbLayout": false,
"showNetwork": true,
"showWifi": true,
"showBluetooth": true,
"showBattery": true,
"showLockStatus": true
},
"clock": { "clock": {
"background": false, "background": false,
"showDate": false, "showDate": false,
"showIcon": true "showIcon": true
}, },
"dragThreshold": 20,
"entries": [ "entries": [
{ {
"id": "logo", "id": "logo",
@ -427,66 +515,12 @@ For example, to disable the bar on DP-1:
"enabled": true "enabled": true
} }
], ],
"persistent": true, "excludedScreens": []
"popouts": {
"activeWindow": true,
"statusIcons": true,
"tray": true
},
"scrollActions": {
"brightness": true,
"workspaces": true,
"volume": true
},
"showOnHover": true,
"status": {
"showAudio": false,
"showBattery": true,
"showBluetooth": true,
"showKbLayout": false,
"showMicrophone": false,
"showNetwork": true,
"showWifi": true,
"showLockStatus": true
},
"tray": {
"background": false,
"compact": false,
"iconSubs": [],
"recolour": false
},
"workspaces": {
"activeIndicator": true,
"activeLabel": "󰮯",
"activeTrail": false,
"label": " ",
"occupiedBg": false,
"occupiedLabel": "󰮯",
"perMonitorWorkspaces": true,
"showWindows": true,
"shown": 5,
"specialWorkspaceIcons": [
{
"name": "steam",
"icon": "sports_esports"
}
],
"windowIcons": [
{
"regex": "steam(_app_(default|[0-9]+))?",
"icon": "sports_esports"
}
]
},
"excludedScreens": [""],
"activeWindow": {
"inverted": false
}
}, },
"border": { "border": {
"thickness": 10,
"rounding": 25, "rounding": 25,
"smoothing": 32, "smoothing": 20
"thickness": 10
}, },
"dashboard": { "dashboard": {
"enabled": true, "enabled": true,
@ -495,11 +529,37 @@ For example, to disable the bar on DP-1:
"showMedia": true, "showMedia": true,
"showPerformance": true, "showPerformance": true,
"showWeather": true, "showWeather": true,
"mediaUpdateInterval": 500,
"resourceUpdateInterval": 1000,
"dragThreshold": 50, "dragThreshold": 50,
"mediaUpdateInterval": 500 "performance": {
"showBattery": true,
"showGpu": true,
"showCpu": true,
"showMemory": true,
"showStorage": true,
"showNetwork": true
}
}, },
"launcher": { "launcher": {
"enabled": true,
"showOnHover": false,
"maxShown": 7,
"maxWallpapers": 9,
"specialPrefix": "@",
"actionPrefix": ">", "actionPrefix": ">",
"enableDangerousActions": false,
"dragThreshold": 50,
"vimKeybinds": false,
"favouriteApps": [],
"hiddenApps": [],
"useFuzzy": {
"apps": false,
"actions": false,
"schemes": false,
"variants": false,
"wallpapers": false
},
"actions": [ "actions": [
{ {
"name": "Calculator", "name": "Calculator",
@ -533,14 +593,6 @@ For example, to disable the bar on DP-1:
"enabled": true, "enabled": true,
"dangerous": false "dangerous": false
}, },
{
"name": "Transparency",
"icon": "opacity",
"description": "Change shell transparency",
"command": ["autocomplete", "transparency"],
"enabled": false,
"dangerous": false
},
{ {
"name": "Random", "name": "Random",
"icon": "casino", "icon": "casino",
@ -609,71 +661,57 @@ For example, to disable the bar on DP-1:
"name": "Settings", "name": "Settings",
"icon": "settings", "icon": "settings",
"description": "Configure the shell", "description": "Configure the shell",
"command": ["caelestia", "shell", "controlCenter", "open"], "command": ["caelestia", "shell", "nexus", "open"],
"enabled": true, "enabled": true,
"dangerous": false "dangerous": false
} }
], ]
"dragThreshold": 50,
"vimKeybinds": false,
"enableDangerousActions": false,
"maxShown": 7,
"maxWallpapers": 9,
"specialPrefix": "@",
"useFuzzy": {
"apps": false,
"actions": false,
"schemes": false,
"variants": false,
"wallpapers": false
},
"showOnHover": false,
"favouriteApps": [],
"hiddenApps": []
}, },
"lock": { "lock": {
"recolourLogo": false, "recolourLogo": true,
"enableFprint": true,
"maxFprintTries": 3,
"hideNotifs": false "hideNotifs": false
}, },
"nexus": {
"wallpapersPerRow": 4,
"networkRescanInterval": 15000
},
"notifs": { "notifs": {
"actionOnClick": false, "expire": true,
"clearThreshold": 0.3, "fullscreen": "on",
"defaultExpireTimeout": 5000, "defaultExpireTimeout": 5000,
"fullscreenExpireTimeout": 2000,
"clearThreshold": 0.3,
"expandThreshold": 20, "expandThreshold": 20,
"openExpanded": false, "actionOnClick": false,
"expire": false "groupPreviewNum": 3,
"openExpanded": false
}, },
"osd": { "osd": {
"enabled": true, "enabled": true,
"hideDelay": 2000,
"enableBrightness": true, "enableBrightness": true,
"enableMicrophone": false, "enableMicrophone": false
"hideDelay": 2000
},
"paths": {
"mediaGif": "root:/assets/bongocat.gif",
"sessionGif": "root:/assets/kurukuru.gif",
"noNotifsPic": "root:/assets/dino.png",
"lockNoNotifsPic": "root:/assets/dino.png",
"wallpaperDir": "~/Pictures/Wallpapers",
"lyricsDir": "~/Music/lyrics"
}, },
"services": { "services": {
"audioIncrement": 0.1,
"brightnessIncrement": 0.1,
"maxVolume": 1.0,
"defaultPlayer": "Spotify",
"gpuType": "",
"playerAliases": [{ "from": "com.github.th_ch.youtube_music", "to": "YT Music" }],
"weatherLocation": "", "weatherLocation": "",
"useFahrenheit": false, "useFahrenheit": false,
"useFahrenheitPerformance": false, "useFahrenheitPerformance": false,
"useTwelveHourClock": false, "useTwelveHourClock": false,
"gpuType": "",
"visualiserBars": 60,
"audioIncrement": 0.1,
"brightnessIncrement": 0.1,
"maxVolume": 1.0,
"smartScheme": true, "smartScheme": true,
"visualiserBars": 45 "defaultPlayer": "Spotify",
"playerAliases": [{ "from": "com.github.th_ch.youtube_music", "to": "YT Music" }],
"lyricsBackend": "Auto"
}, },
"session": { "session": {
"dragThreshold": 30,
"enabled": true, "enabled": true,
"dragThreshold": 30,
"vimKeybinds": false, "vimKeybinds": false,
"icons": { "icons": {
"logout": "logout", "logout": "logout",
@ -689,28 +727,29 @@ For example, to disable the bar on DP-1:
} }
}, },
"sidebar": { "sidebar": {
"dragThreshold": 80, "enabled": true,
"enabled": true "dragThreshold": 80
}, },
"utilities": { "utilities": {
"enabled": true, "enabled": true,
"maxToasts": 4, "maxToasts": 4,
"toasts": { "toasts": {
"audioInputChanged": true, "fullscreen": "off",
"audioOutputChanged": true,
"capsLockChanged": true,
"chargingChanged": true,
"configLoaded": true, "configLoaded": true,
"dndChanged": true, "chargingChanged": true,
"gameModeChanged": true, "gameModeChanged": true,
"dndChanged": true,
"audioOutputChanged": true,
"audioInputChanged": true,
"capsLockChanged": true,
"numLockChanged": true,
"kbLayoutChanged": true, "kbLayoutChanged": true,
"kbLimit": true, "kbLimit": true,
"numLockChanged": true,
"vpnChanged": true, "vpnChanged": true,
"nowPlaying": false "nowPlaying": false
}, },
"vpn": { "vpn": {
"enabled": true, "enabled": false,
"provider": [ "provider": [
{ {
"name": "wireguard", "name": "wireguard",
@ -734,8 +773,8 @@ For example, to disable the bar on DP-1:
"enabled": true "enabled": true
}, },
{ {
"enabled": true, "id": "settings",
"id": "settings" "enabled": true
}, },
{ {
"id": "gameMode", "id": "gameMode",
@ -747,9 +786,17 @@ For example, to disable the bar on DP-1:
}, },
{ {
"id": "vpn", "id": "vpn",
"enabled": true "enabled": false
} }
] ]
},
"paths": {
"wallpaperDir": "~/Pictures/Wallpapers",
"lyricsDir": "~/Music/lyrics/",
"sessionGif": "root:/assets/kurukuru.gif",
"mediaGif": "root:/assets/bongocat.gif",
"noNotifsPic": "root:/assets/dino.png",
"lockNoNotifsPic": "root:/assets/dino.png"
} }
} }
``` ```

View file

@ -0,0 +1,91 @@
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -1,26 +0,0 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float fadeMargin;
};
layout(binding = 1) uniform sampler2D source;
void main() {
vec4 tex = texture(source, qt_TexCoord0);
float factor = 1.0;
float margin = 0.1;
if (qt_TexCoord0.y < margin) {
factor = qt_TexCoord0.y / margin;
} else if (qt_TexCoord0.y > (1.0 - margin)) {
factor = (1.0 - qt_TexCoord0.y) / margin;
}
fragColor = tex * factor * qt_Opacity;
}

Binary file not shown.

View file

@ -1,19 +0,0 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
// qt_Matrix and qt_Opacity must always be both present
// if the built-in vertex shader is used.
mat4 qt_Matrix;
float qt_Opacity;
};
layout(binding = 1) uniform sampler2D source;
layout(binding = 2) uniform sampler2D maskSource;
void main()
{
fragColor = texture(source, qt_TexCoord0.st) * (texture(maskSource, qt_TexCoord0.st).a) * qt_Opacity;
}

Binary file not shown.

BIN
assets/wallpaper.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

View file

@ -13,33 +13,48 @@ NumberAnimation {
EmphasizedExtraLarge, EmphasizedExtraLarge,
FastSpatial, FastSpatial,
DefaultSpatial, DefaultSpatial,
SlowSpatial SlowSpatial,
FastEffects,
DefaultEffects,
SlowEffects
} }
property int type: Anim.Standard property int type: Anim.DefaultSpatial
duration: { duration: {
if (type < Anim.StandardSmall || type > Anim.SlowSpatial) if (type < Anim.StandardSmall || type > Anim.SlowEffects)
return Tokens.anim.durations.normal; return Tokens.anim.durations.normal;
if (type == Anim.FastSpatial) if (type === Anim.FastSpatial)
return Tokens.anim.durations.expressiveFastSpatial; return Tokens.anim.durations.expressiveFastSpatial;
if (type == Anim.DefaultSpatial) if (type === Anim.DefaultSpatial)
return Tokens.anim.durations.expressiveDefaultSpatial; return Tokens.anim.durations.expressiveDefaultSpatial;
if (type == Anim.SlowSpatial) if (type === Anim.SlowSpatial)
return Tokens.anim.durations.expressiveSlowSpatial; return Tokens.anim.durations.expressiveSlowSpatial;
if (type === Anim.FastEffects)
return Tokens.anim.durations.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Tokens.anim.durations.expressiveDefaultEffects;
if (type === Anim.SlowEffects)
return Tokens.anim.durations.expressiveSlowEffects;
const types = ["small", "normal", "large", "extraLarge"]; const types = ["small", "normal", "large", "extraLarge"];
const idx = type % 4; // 0-7 are the 4 standard types const idx = type % 4; // 0-7 are the 4 standard types
return Tokens.anim.durations[types[idx]]; return Tokens.anim.durations[types[idx]];
} }
easing: { easing: {
if (type == Anim.FastSpatial) if (type === Anim.FastSpatial)
return Tokens.anim.expressiveFastSpatial; return Tokens.anim.expressiveFastSpatial;
if (type == Anim.DefaultSpatial) if (type === Anim.DefaultSpatial)
return Tokens.anim.expressiveDefaultSpatial; return Tokens.anim.expressiveDefaultSpatial;
if (type == Anim.SlowSpatial) if (type === Anim.SlowSpatial)
return Tokens.anim.expressiveSlowSpatial; return Tokens.anim.expressiveSlowSpatial;
if (type === Anim.FastEffects)
return Tokens.anim.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Tokens.anim.expressiveDefaultEffects;
if (type === Anim.SlowEffects)
return Tokens.anim.expressiveSlowEffects;
if (type >= Anim.EmphasizedSmall && type <= Anim.EmphasizedExtraLarge) if (type >= Anim.EmphasizedSmall && type <= Anim.EmphasizedExtraLarge)
return Tokens.anim.emphasized; return Tokens.anim.emphasized;

42
components/AnimLoader.qml Normal file
View file

@ -0,0 +1,42 @@
import QtQuick
Loader {
id: root
property Component sourceComp
property bool isComplete
property int outAnimType: Anim.FastEffects
property int inAnimType: Anim.DefaultEffects
asynchronous: true
Component.onCompleted: {
isComplete = true;
sourceComponent = sourceComp;
}
onSourceCompChanged: {
if (isComplete)
anim.restart();
}
SequentialAnimation {
id: anim
running: false
Anim {
target: root
property: "opacity"
to: 0
type: root.outAnimType
}
ScriptAction {
script: root.sourceComponent = root.sourceComp
}
Anim {
target: root
property: "opacity"
to: 1
type: root.inAnimType
}
}
}

View file

@ -2,6 +2,6 @@ import QtQuick
import Caelestia.Config import Caelestia.Config
ColorAnimation { ColorAnimation {
duration: Tokens.anim.durations.normal duration: Tokens.anim.durations.expressiveSlowEffects
easing: Tokens.anim.standard easing: Tokens.anim.expressiveSlowEffects
} }

View file

@ -9,22 +9,20 @@ ColumnLayout {
required property string icon required property string icon
required property string title required property string title
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
animate: true animate: true
text: root.icon text: root.icon
font.pointSize: Tokens.font.size.extraLarge * 3 fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).weight(Font.Bold).build()
font.bold: true
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
animate: true animate: true
text: root.title text: root.title
font.pointSize: Tokens.font.size.large font: Tokens.font.title.builders.medium.weight(Font.Bold).build()
font.bold: true
} }
} }

View file

@ -9,7 +9,7 @@ ColumnLayout {
required property var deviceDetails required property var deviceDetails
spacing: Tokens.spacing.small / 2 spacing: Tokens.spacing.extraSmall
StyledText { StyledText {
text: qsTr("IP Address") text: qsTr("IP Address")
@ -18,40 +18,40 @@ ColumnLayout {
StyledText { StyledText {
text: root.deviceDetails?.ipAddress || qsTr("Not available") text: root.deviceDetails?.ipAddress || qsTr("Not available")
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
StyledText { StyledText {
Layout.topMargin: Tokens.spacing.normal Layout.topMargin: Tokens.spacing.medium
text: qsTr("Subnet Mask") text: qsTr("Subnet Mask")
} }
StyledText { StyledText {
text: root.deviceDetails?.subnet || qsTr("Not available") text: root.deviceDetails?.subnet || qsTr("Not available")
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
StyledText { StyledText {
Layout.topMargin: Tokens.spacing.normal Layout.topMargin: Tokens.spacing.medium
text: qsTr("Gateway") text: qsTr("Gateway")
} }
StyledText { StyledText {
text: root.deviceDetails?.gateway || qsTr("Not available") text: root.deviceDetails?.gateway || qsTr("Not available")
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
StyledText { StyledText {
Layout.topMargin: Tokens.spacing.normal Layout.topMargin: Tokens.spacing.medium
text: qsTr("DNS Servers") text: qsTr("DNS Servers")
} }
StyledText { StyledText {
text: (root.deviceDetails && root.deviceDetails.dns && root.deviceDetails.dns.length > 0) ? root.deviceDetails.dns.join(", ") : qsTr("Not available") text: (root.deviceDetails && root.deviceDetails.dns && root.deviceDetails.dns.length > 0) ? root.deviceDetails.dns.join(", ") : qsTr("Not available")
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
} }

View file

@ -1,16 +1,11 @@
import QtQuick
import Caelestia.Config import Caelestia.Config
import qs.services import qs.services
StyledText { StyledText {
property real fill property real fill
property int grade: Colours.light ? 0 : -25 property int grade: Colours.light ? 0 : -25
property font fontStyle: Tokens.font.icon.small
font.family: Tokens.font.family.material font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
font.pointSize: Tokens.font.size.larger
font.variableAxes: ({
FILL: fill.toFixed(1),
GRAD: grade,
opsz: fontInfo.pixelSize,
wght: fontInfo.weight
})
} }

View file

@ -11,16 +11,16 @@ ColumnLayout {
required property string value required property string value
property bool showTopMargin: false property bool showTopMargin: false
spacing: Tokens.spacing.small / 2 spacing: Tokens.spacing.extraSmall
StyledText { StyledText {
Layout.topMargin: root.showTopMargin ? Tokens.spacing.normal : 0 Layout.topMargin: root.showTopMargin ? Tokens.spacing.medium : 0
text: root.label text: root.label
} }
StyledText { StyledText {
text: root.value text: root.value
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
} }

View file

@ -8,13 +8,13 @@ StyledRect {
id: root id: root
default property alias content: contentColumn.data default property alias content: contentColumn.data
property real contentSpacing: Tokens.spacing.larger property real contentSpacing: Tokens.spacing.large
property bool alignTop: false property bool alignTop: false
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: contentColumn.implicitHeight + Tokens.padding.large * 2 implicitHeight: contentColumn.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: Colours.transparency.enabled ? Colours.layer(Colours.palette.m3surfaceContainer, 2) : Colours.palette.m3surfaceContainerHigh color: Colours.transparency.enabled ? Colours.layer(Colours.palette.m3surfaceContainer, 2) : Colours.palette.m3surfaceContainerHigh
ColumnLayout { ColumnLayout {

View file

@ -13,10 +13,9 @@ ColumnLayout {
spacing: 0 spacing: 0
StyledText { StyledText {
Layout.topMargin: Tokens.spacing.large Layout.topMargin: Tokens.spacing.largeIncreased
text: root.title text: root.title
font.pointSize: Tokens.font.size.larger font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
font.weight: 500
} }
StyledText { StyledText {

View file

@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import Caelestia
import Caelestia.Config import Caelestia.Config
import qs.services import qs.services
@ -8,10 +9,11 @@ MouseArea {
property bool disabled property bool disabled
property bool showHoverBackground: true property bool showHoverBackground: true
property bool manualPressOverride
readonly property alias rect: base readonly property alias rect: base
property bool shapeMorph property bool shapeMorph
property real stateOpacity: pressed ? 0.1 : containsMouse ? 0.08 : 0 property real stateOpacity: containsMouse ? 0.08 : 0
property real pressX: width / 2 property real pressX: width / 2
property real pressY: height / 2 property real pressY: height / 2
@ -29,7 +31,7 @@ MouseArea {
const d2 = distSq(width, 0); const d2 = distSq(width, 0);
const d3 = distSq(0, height); const d3 = distSq(0, height);
const d4 = distSq(width, height); const d4 = distSq(width, height);
return Math.sqrt(Math.max(d1, d2, d3, d4)) * (shapeMorph ? 1.16 : 1); return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
} }
property real endRadiusAtPress property real endRadiusAtPress
@ -59,12 +61,17 @@ MouseArea {
onPressed: e => press(e.x, e.y) onPressed: e => press(e.x, e.y)
onPressedChanged: { onPressedChanged: {
if (!pressed && !rippleAnim.running && circle.opacity > 0) if (!(pressed || manualPressOverride) && !rippleAnim.running && circle.opacity > 0)
fadeAnim.start();
}
onManualPressOverrideChanged: {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start(); fadeAnim.start();
} }
onCircleRadiusChanged: { onCircleRadiusChanged: {
if (!pressed && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running) if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start(); fadeAnim.start();
} }
@ -75,7 +82,7 @@ MouseArea {
target: root target: root
property: "circleRadius" property: "circleRadius"
to: root.endRadius to: root.endRadius
easing: Tokens.anim.expressiveSlowEffects easing: Tokens.anim.standard
duration: Tokens.anim.durations.expressiveSlowEffects * 2 duration: Tokens.anim.durations.expressiveSlowEffects * 2
} }
@ -85,8 +92,7 @@ MouseArea {
target: circle target: circle
property: "opacity" property: "opacity"
to: 0 to: 0
easing: Tokens.anim.expressiveSlowEffects type: Anim.SlowEffects
duration: Tokens.anim.durations.expressiveSlowEffects
} }
StyledRect { StyledRect {
@ -95,8 +101,14 @@ MouseArea {
anchors.fill: parent anchors.fill: parent
opacity: root.stateOpacity opacity: root.stateOpacity
color: Colours.palette.m3onSurface color: Colours.palette.m3onSurface
// Pick up radius from parent if it has one (parent can be anything with a radius property) // Pick up radius from parent if it has one (parent can be anything with radius props)
radius: root.parent?.radius ?? 0 // qmllint disable missing-property // qmllint disable missing-property
radius: root.parent?.radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
// qmllint enable missing-property
} }
Shape { Shape {
@ -122,12 +134,12 @@ MouseArea {
color: Qt.alpha(base.color, 1) color: Qt.alpha(base.color, 1)
} }
GradientStop { GradientStop {
position: 0.99 position: CUtils.clamp(1 - 0.2 * root.endRadius / root.circleRadius, 0.01, 0.99)
color: Qt.alpha(base.color, 1) color: Qt.alpha(base.color, 1)
} }
GradientStop { GradientStop {
position: 1 position: 1
color: Qt.alpha(base.color, 0) color: Qt.alpha(base.color, CUtils.clamp((root.circleRadius / root.endRadius - 0.9) / 0.1, 0, 1))
} }
} }
@ -135,14 +147,14 @@ MouseArea {
startY: 0 startY: 0
PathLine { PathLine {
x: root.width - root.clamp(base.topLeftRadius) x: root.width - root.clamp(base.topRightRadius)
y: 0 y: 0
} }
PathArc { PathArc {
relativeX: root.clamp(base.topLeftRadius) relativeX: root.clamp(base.topRightRadius)
relativeY: root.clamp(base.topLeftRadius) relativeY: root.clamp(base.topRightRadius)
radiusX: root.clamp(base.topLeftRadius) radiusX: root.clamp(base.topRightRadius)
radiusY: root.clamp(base.topLeftRadius) radiusY: root.clamp(base.topRightRadius)
} }
PathLine { PathLine {
x: root.width x: root.width
@ -179,8 +191,7 @@ MouseArea {
Behavior on stateOpacity { Behavior on stateOpacity {
Anim { Anim {
easing: Tokens.anim.expressiveDefaultEffects type: Anim.DefaultEffects
duration: Tokens.anim.durations.expressiveDefaultEffects
} }
} }
} }

View file

@ -8,16 +8,11 @@ Text {
id: root id: root
property bool animate: false property bool animate: false
property string animateProp: "scale"
property real animateFrom: 0
property real animateTo: 1
property int animateDuration: Tokens.anim.durations.normal
renderType: Text.NativeRendering renderType: Text.NativeRendering
textFormat: Text.PlainText textFormat: Text.PlainText
color: Colours.palette.m3onSurface color: Colours.palette.m3onSurface
font.family: Tokens.font.family.sans font: Tokens.font.body.small
font.pointSize: Tokens.font.size.smaller
Behavior on color { Behavior on color {
CAnim {} CAnim {}
@ -28,20 +23,18 @@ Text {
SequentialAnimation { SequentialAnimation {
Anim { Anim {
to: root.animateFrom target: root
easing: Tokens.anim.standardAccel property: "opacity"
to: 0
type: Anim.FastEffects
} }
PropertyAction {} PropertyAction {}
Anim { Anim {
to: root.animateTo
easing: Tokens.anim.standardDecel
}
}
}
component Anim: NumberAnimation {
target: root target: root
property: root.animateProp property: "opacity"
duration: root.animateDuration / 2 to: 1
type: Anim.DefaultEffects
}
}
} }
} }

View file

@ -4,11 +4,28 @@ import qs.components
Flickable { Flickable {
id: root id: root
property bool doneFakeFlick
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
onRunningChanged: {
if (!running && !root.doneFakeFlick) {
root.doneFakeFlick = true;
root.flick(1, 1);
root.flick(-1, -1);
Qt.callLater(() => root.cancelFlick());
}
}
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
Timer {
running: root.doneFakeFlick
interval: 10
onTriggered: root.doneFakeFlick = false
}
} }

View file

@ -4,11 +4,28 @@ import qs.components
ListView { ListView {
id: root id: root
property bool doneFakeFlick
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
onRunningChanged: {
if (!running && !root.doneFakeFlick) {
root.doneFakeFlick = true;
root.flick(1, 1);
root.flick(-1, -1);
Qt.callLater(() => root.cancelFlick());
}
}
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
Timer {
running: root.doneFakeFlick
interval: 10
onTriggered: root.doneFakeFlick = false
}
} }

View file

@ -0,0 +1,72 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.components
import qs.components.effects
StyledFlickable {
id: root
property real fadeAmount: 0.2
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
return false;
if (isStart)
return visibleArea.yPosition > 0;
return visibleArea.yPosition + visibleArea.heightRatio < 1;
}
flickableDirection: Flickable.VerticalFlick
layer.enabled: true
layer.effect: Mask {
maskSource: mask
Rectangle {
id: mask
anchors.fill: parent
visible: false
layer.enabled: true
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
position: 0
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
}
GradientStop {
position: root.fadeAmount
color: Qt.rgba(0, 0, 0, 1)
}
GradientStop {
position: 1 - root.fadeAmount
color: Qt.rgba(0, 0, 0, 1)
}
GradientStop {
position: 1
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
}
}
}
}
Behavior on topFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
Behavior on bottomFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
}

View file

@ -0,0 +1,95 @@
import QtQuick
import Caelestia.Config
import qs.components
import qs.services
StyledRect {
id: root
enum ButtonType {
Filled,
Tonal,
Text
}
property bool checked
property alias disabled: stateLayer.disabled
property bool isToggle
property bool isRound
property bool radiusMorph: true
property alias shapeMorph: stateLayer.shapeMorph
property bool fillWidth // For ButtonRow
property font font
property int type: ButtonBase.Filled
property real padding
property real horizontalPadding: padding
property real verticalPadding: padding
readonly property alias pressed: stateLayer.pressed
readonly property alias hovered: stateLayer.containsMouse
readonly property alias stateLayer: stateLayer
readonly property alias radiusAnim: radiusAnim
property color activeColour
property color inactiveColour
property color activeOnColour
property color inactiveOnColour
property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
property color disabledOnColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
property bool internalChecked
property real shapeMorphExpansion: shapeMorph && pressed ? 24 : 0 // Apparently it's always 24px no matter the width of the button
readonly property color onColour: disabled ? disabledOnColour : internalChecked ? activeOnColour : inactiveOnColour
property real pressedRadius: Tokens.rounding.small
property real checkedRadius: Tokens.rounding.medium
property real defaultRadius: Tokens.rounding.large
signal clicked
onCheckedChanged: internalChecked = checked
radius: {
if (radiusMorph && pressed)
return pressedRadius;
if (internalChecked)
return checkedRadius;
if (isRound)
return (height || implicitHeight) / 2 * Math.min(1, Tokens.rounding.scale);
return defaultRadius;
}
color: type === ButtonBase.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
// Make size required so we don't forget to set it
required implicitWidth
required implicitHeight
StateLayer {
id: stateLayer
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
disabled: root.disabled
onClicked: {
if (root.isToggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
}
Behavior on radius {
Anim {
id: radiusAnim
type: Anim.DefaultEffects
}
}
Behavior on shapeMorphExpansion {
Anim {
type: Anim.FastSpatial
}
}
}

View file

@ -19,8 +19,8 @@ BusyIndicator {
Completing Completing
} }
property real implicitSize: Tokens.font.size.normal * 3 property real implicitSize: Tokens.font.body.medium.pointSize * 3
property real strokeWidth: Tokens.padding.small * 0.8 property real strokeWidth: Tokens.padding.extraSmall
property color fgColour: Colours.palette.m3primary property color fgColour: Colours.palette.m3primary
property color bgColour: Colours.palette.m3secondaryContainer property color bgColour: Colours.palette.m3secondaryContainer
@ -63,6 +63,7 @@ BusyIndicator {
transitions: Transition { transitions: Transition {
Anim { Anim {
type: Anim.DefaultEffects
properties: "opacity,internalStrokeWidth" properties: "opacity,internalStrokeWidth"
duration: manager.completeEndDuration * Tokens.anim.durations.scale duration: manager.completeEndDuration * Tokens.anim.durations.scale
} }
@ -77,6 +78,7 @@ BusyIndicator {
rotation: manager.rotation rotation: manager.rotation
startAngle: manager.startFraction * 360 startAngle: manager.startFraction * 360
value: manager.endFraction - manager.startFraction value: manager.endFraction - manager.startFraction
hasEndIndicator: false
} }
CircularIndicatorManager { CircularIndicatorManager {

View file

@ -1,69 +1,122 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import Caelestia.Components
import Caelestia.Config import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
Shape { Item {
id: root id: root
property real value property real value
property int startAngle: -90 property int startAngle: -90
property int strokeWidth: Tokens.padding.smaller property int sweepAngle: 360
property int strokeWidth: Tokens.padding.small
property int padding: 0 property int padding: 0
property int spacing: Tokens.spacing.small property int spacing: Tokens.spacing.small
property color fgColour: Colours.palette.m3primary property color fgColour: Colours.palette.m3primary
property color bgColour: Colours.palette.m3secondaryContainer property color bgColour: Colours.palette.m3secondaryContainer
property alias hasEndIndicator: dot.active
property bool wavy: false
property alias waveFrequency: wave.frequency
property alias waveAmplitude: wave.amplitudeMultiplier
property bool wavePaused
property alias waveDuration: waveProgAnim.duration
readonly property real size: Math.min(width, height) readonly property real size: Math.min(width, height)
readonly property real arcRadius: (size - padding - strokeWidth) / 2 readonly property real arcRadius: (size - padding - strokeWidth * (1 + waveAmplitude * 2)) / 2
readonly property real vValue: value || 1 / 360 property real clampedVal: Math.max(1 / 360, Math.min(1, isNaN(value) ? 0 : value)) // Not readonly for animations
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI) readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
readonly property real dotAngleRad: (startAngle + sweepAngle - gapAngle * (sweepAngle < 360 ? 0 : 1)) * Math.PI / 180
readonly property real thickness: strokeWidth * (1 + waveAmplitude) * 2 // For consumers
property real implicitSize
implicitWidth: implicitSize
implicitHeight: implicitSize
Shape {
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
asynchronous: true asynchronous: true
opacity: Math.min(1, remainingArc.sweepAngle)
ShapePath { ShapePath {
fillColor: "transparent" fillColor: "transparent"
strokeColor: root.bgColour strokeColor: root.bgColour
strokeWidth: root.strokeWidth strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap capStyle: ShapePath.RoundCap
PathAngleArc { PathAngleArc {
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle id: remainingArc
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
radiusX: root.arcRadius radiusX: root.arcRadius
radiusY: root.arcRadius radiusY: root.arcRadius
centerX: root.size / 2 centerX: root.size / 2
centerY: root.size / 2 centerY: root.size / 2
startAngle: root.startAngle + root.clampedVal * root.sweepAngle + root.gapAngle
sweepAngle: Math.max(1 / 360, root.sweepAngle * (1 - root.clampedVal) - root.gapAngle * (root.sweepAngle < 360 ? 1 : 2))
} }
Behavior on strokeColor { Behavior on strokeColor {
CAnim { CAnim {}
duration: Tokens.anim.durations.large
} }
} }
} }
ShapePath { WavyLine {
fillColor: "transparent" id: wave
strokeColor: root.fgColour
strokeWidth: root.strokeWidth
capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
PathAngleArc { anchors.fill: parent
anchors.margins: -lineWidth * amplitudeMultiplier
lineWidth: root.strokeWidth
color: root.fgColour
pathType: WavyLine.Arc
radius: root.arcRadius
startAngle: root.startAngle startAngle: root.startAngle
sweepAngle: 360 * root.vValue fullAngle: root.sweepAngle
radiusX: root.arcRadius value: root.clampedVal
radiusY: root.arcRadius frequency: 8
centerX: root.size / 2 amplitudeMultiplier: root.wavy ? 0.5 : 0
centerY: root.size / 2
Anim on waveProgress {
id: waveProgAnim
running: true
paused: root.wavePaused || wave.amplitudeMultiplier === 0
from: 0
to: 1
duration: 2000
easing.type: Easing.Linear
loops: Animation.Infinite
} }
Behavior on strokeColor { Behavior on color {
CAnim { CAnim {}
duration: Tokens.anim.durations.large
} }
Behavior on amplitudeMultiplier {
Anim {
type: Anim.DefaultEffects
}
}
}
Loader {
id: dot
x: root.size / 2 + root.arcRadius * Math.cos(root.dotAngleRad) - width / 2
y: root.size / 2 + root.arcRadius * Math.sin(root.dotAngleRad) - height / 2
sourceComponent: StyledRect {
radius: Tokens.rounding.full
color: root.fgColour
opacity: Math.min(1, remainingArc.sweepAngle)
implicitWidth: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
implicitHeight: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
} }
} }
} }

View file

@ -24,7 +24,7 @@ ColumnLayout {
id: sectionHeaderItem id: sectionHeaderItem
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.max(titleRow.implicitHeight + Tokens.padding.normal * 2, 48) Layout.preferredHeight: Math.max(titleRow.implicitHeight + Tokens.padding.medium * 2, 48)
RowLayout { RowLayout {
id: titleRow id: titleRow
@ -32,14 +32,13 @@ ColumnLayout {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.padding.normal anchors.leftMargin: Tokens.padding.medium
anchors.rightMargin: Tokens.padding.normal anchors.rightMargin: Tokens.padding.medium
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
text: root.title text: root.title
font.pointSize: Tokens.font.size.larger font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
font.weight: 500
} }
Item { Item {
@ -50,7 +49,7 @@ ColumnLayout {
text: "expand_more" text: "expand_more"
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal fontStyle: Tokens.font.icon.medium
Behavior on rotation { Behavior on rotation {
Anim { Anim {
@ -63,7 +62,7 @@ ColumnLayout {
StateLayer { StateLayer {
anchors.fill: parent anchors.fill: parent
color: Colours.palette.m3onSurface color: Colours.palette.m3onSurface
radius: Tokens.rounding.normal radius: Tokens.rounding.large
showHoverBackground: false showHoverBackground: false
onClicked: { onClicked: {
root.toggleRequested(); root.toggleRequested();
@ -76,7 +75,7 @@ ColumnLayout {
id: contentWrapper id: contentWrapper
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Tokens.spacing.small * 2) : 0 Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Tokens.spacing.large) : 0
clip: true clip: true
Behavior on Layout.preferredHeight { Behavior on Layout.preferredHeight {
@ -87,13 +86,15 @@ ColumnLayout {
id: backgroundRect id: backgroundRect
anchors.fill: parent anchors.fill: parent
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: Colours.transparency.enabled ? Colours.layer(Colours.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3surfaceContainer) color: Colours.transparency.enabled ? Colours.layer(Colours.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3surfaceContainer)
opacity: root.showBackground && root.expanded ? 1.0 : 0.0 opacity: root.showBackground && root.expanded ? 1.0 : 0.0
visible: root.showBackground visible: root.showBackground
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -103,26 +104,28 @@ ColumnLayout {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
y: Tokens.spacing.small y: Tokens.spacing.small
anchors.leftMargin: Tokens.padding.normal anchors.leftMargin: Tokens.padding.medium
anchors.rightMargin: Tokens.padding.normal anchors.rightMargin: Tokens.padding.medium
anchors.bottomMargin: Tokens.spacing.small anchors.bottomMargin: Tokens.spacing.small
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
opacity: root.expanded ? 1.0 : 0.0 opacity: root.expanded ? 1.0 : 0.0
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
StyledText { StyledText {
id: descriptionText id: descriptionText
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: root.description !== "" ? Tokens.spacing.smaller : 0 Layout.topMargin: root.description !== "" ? Tokens.spacing.medium : 0
Layout.bottomMargin: root.description !== "" ? Tokens.spacing.small : 0 Layout.bottomMargin: root.description !== "" ? Tokens.spacing.small : 0
visible: root.description !== "" visible: root.description !== ""
text: root.description text: root.description
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
} }

View file

@ -73,23 +73,23 @@ RowLayout {
root.isEditing = false; root.isEditing = false;
} }
padding: Tokens.padding.small padding: Tokens.padding.extraSmall
leftPadding: Tokens.padding.normal leftPadding: Tokens.padding.medium
rightPadding: Tokens.padding.normal rightPadding: Tokens.padding.medium
background: StyledRect { background: StyledRect {
implicitWidth: 100 implicitWidth: 100
radius: Tokens.rounding.small radius: Tokens.rounding.medium
color: Colours.tPalette.m3surfaceContainerHigh color: Colours.tPalette.m3surfaceContainerHigh
} }
} }
StyledRect { StyledRect {
radius: Tokens.rounding.small radius: Tokens.rounding.medium
color: Colours.palette.m3primary color: Colours.palette.m3primary
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: upIcon.implicitHeight + Tokens.padding.small * 2 implicitHeight: upIcon.implicitHeight + Tokens.padding.small
StateLayer { StateLayer {
id: upState id: upState
@ -120,11 +120,11 @@ RowLayout {
} }
StyledRect { StyledRect {
radius: Tokens.rounding.small radius: Tokens.rounding.medium
color: Colours.palette.m3primary color: Colours.palette.m3primary
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: downIcon.implicitHeight + Tokens.padding.small * 2 implicitHeight: downIcon.implicitHeight + Tokens.padding.small
StateLayer { StateLayer {
id: downState id: downState

View file

@ -67,41 +67,22 @@ Slider {
property bool moving property bool moving
function update(): void {
animate = !moving;
binding.when = moving;
font.pointSize = moving ? Tokens.font.size.small : Tokens.font.size.larger;
font.family = moving ? Tokens.font.family.sans : Tokens.font.family.material;
}
text: root.icon
color: Colours.palette.m3inverseOnSurface
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 1
text: moving ? Math.round(root.value * 100) : root.icon
color: Colours.palette.m3inverseOnSurface
font: moving ? Tokens.font.body.small : Tokens.font.icon.medium
onMovingChanged: anim.restart() Behavior on moving {
Binding {
id: binding
target: icon
property: "text"
value: Math.round(root.value * 100)
when: false
}
SequentialAnimation { SequentialAnimation {
id: anim
Anim { Anim {
target: icon target: icon
property: "scale" property: "scale"
to: 0 to: 0.3
duration: Tokens.anim.durations.normal / 2 duration: Tokens.anim.durations.small / 2
easing: Tokens.anim.standardAccel easing: Tokens.anim.standardAccel
} }
ScriptAction { PropertyAction {}
script: icon.update()
}
Anim { Anim {
target: icon target: icon
property: "scale" property: "scale"
@ -113,6 +94,7 @@ Slider {
} }
} }
} }
}
onPressedChanged: handle.moving = pressed onPressedChanged: handle.moving = pressed

View file

@ -3,80 +3,50 @@ import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
StyledRect { ButtonBase {
id: root id: root
enum Type {
Filled,
Tonal,
Text
}
property alias icon: label.text property alias icon: label.text
property bool checked readonly property alias label: label
property bool toggle
property real padding: type === IconButton.Text ? Tokens.padding.small / 2 : Tokens.padding.smaller
property alias font: label.font
property int type: IconButton.Filled
property bool disabled
property alias stateLayer: stateLayer font: Tokens.font.icon.medium
property alias label: label padding: type === IconButton.Text ? Tokens.padding.extraSmall / 2 : Tokens.padding.small
property alias radiusAnim: radiusAnim
property bool internalChecked activeColour: type === IconButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
property color activeColour: type === IconButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary inactiveColour: {
property color inactiveColour: { if (!isToggle && type === IconButton.Filled)
if (!toggle && type === IconButton.Filled)
return Colours.palette.m3primary; return Colours.palette.m3primary;
return type === IconButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer; return type === IconButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer;
} }
property color activeOnColour: type === IconButton.Filled ? Colours.palette.m3onPrimary : type === IconButton.Tonal ? Colours.palette.m3onSecondary : Colours.palette.m3primary activeOnColour: type === IconButton.Filled ? Colours.palette.m3onPrimary : type === IconButton.Tonal ? Colours.palette.m3onSecondary : Colours.palette.m3primary
property color inactiveOnColour: { inactiveOnColour: {
if (!toggle && type === IconButton.Filled) if (!isToggle && type === IconButton.Filled)
return Colours.palette.m3onPrimary; return Colours.palette.m3onPrimary;
return type === IconButton.Tonal ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurfaceVariant; return type === IconButton.Tonal ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurfaceVariant;
} }
property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
property color disabledOnColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
signal clicked
onCheckedChanged: internalChecked = checked
radius: internalChecked ? Tokens.rounding.small : implicitHeight / 2 * Math.min(1, Tokens.rounding.scale)
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: label.implicitHeight + padding * 2 implicitHeight: {
// Ensure even size so icon is centered properly
StateLayer { const h = label.implicitHeight + padding * 2;
id: stateLayer if (h % 2 !== 0)
return h + 1;
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour return h;
disabled: root.disabled
onClicked: {
if (root.toggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
} }
MaterialIcon { MaterialIcon {
id: label id: label
anchors.centerIn: parent anchors.centerIn: parent
color: root.disabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour anchors.verticalCenterOffset: 1 // AHHHHHHH material symbols whyyyy
fill: !root.toggle || root.internalChecked ? 1 : 0 color: root.onColour
fontStyle: root.font
fill: !root.isToggle || root.internalChecked ? 1 : 0
Behavior on fill { Behavior on fill {
Anim {}
}
}
Behavior on radius {
Anim { Anim {
id: radiusAnim type: Anim.DefaultEffects
}
} }
} }
} }

View file

@ -4,55 +4,40 @@ import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
StyledRect { ButtonBase {
id: root id: root
enum Type {
Filled,
Tonal,
Text
}
property alias icon: iconLabel.text property alias icon: iconLabel.text
property alias text: label.text property alias text: label.text
property bool checked
property bool toggle
property real horizontalPadding: Tokens.padding.normal
property real verticalPadding: Tokens.padding.smaller
property alias font: label.font
property int type: IconTextButton.Filled
property alias stateLayer: stateLayer readonly property alias iconLabel: iconLabel
property alias iconLabel: iconLabel readonly property alias label: label
property alias label: label
property bool internalChecked horizontalPadding: Tokens.padding.medium
property color activeColour: type === IconTextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary verticalPadding: Tokens.padding.small
property color inactiveColour: type === IconTextButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer
property color activeOnColour: type === IconTextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
property color inactiveOnColour: type === IconTextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
signal clicked activeColour: type === TextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
inactiveColour: {
onCheckedChanged: internalChecked = checked if (!isToggle && type === TextButton.Filled)
return Colours.palette.m3primary;
radius: internalChecked ? Tokens.rounding.small : implicitHeight / 2 * Math.min(1, Tokens.rounding.scale) return type === TextButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer;
color: type === IconTextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour }
activeOnColour: {
if (type === TextButton.Text)
return Colours.palette.m3primary;
return type === TextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary;
}
inactiveOnColour: {
if (!isToggle && type === TextButton.Filled)
return Colours.palette.m3onPrimary;
if (type === TextButton.Text)
return Colours.palette.m3primary;
return type === TextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer;
}
implicitWidth: row.implicitWidth + horizontalPadding * 2 implicitWidth: row.implicitWidth + horizontalPadding * 2
implicitHeight: row.implicitHeight + verticalPadding * 2 implicitHeight: row.implicitHeight + verticalPadding * 2
StateLayer {
id: stateLayer
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
onClicked: {
if (root.toggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
}
RowLayout { RowLayout {
id: row id: row
@ -64,11 +49,18 @@ StyledRect {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575) Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour color: root.onColour
fill: root.internalChecked ? 1 : 0 fill: root.internalChecked ? 1 : 0
fontStyle: {
const f = Qt.font(root.font);
f.pointSize = Math.round(root.font.pointSize * 1.1);
return f;
}
Behavior on fill { Behavior on fill {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -77,11 +69,8 @@ StyledRect {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.topMargin: -Math.round(iconLabel.fontInfo.pointSize * 0.0575) Layout.topMargin: -Math.round(iconLabel.fontInfo.pointSize * 0.0575)
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour color: root.onColour
font: root.font
} }
} }
Behavior on radius {
Anim {}
}
} }

View file

@ -0,0 +1,116 @@
import QtQuick
import Quickshell
import M3Shapes
import qs.components
import qs.services
MaterialShape {
id: root
property list<int> shapes: {
if (containsIcon)
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pentagon, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
}
property int shapeIndex
property real cRotation
property real lRotation
property real thisLRotation
property bool containsIcon
property bool animated: true
property int morphAnimRotation: 60
property real morphScale: 0.14
property alias rotateAnimDuration: rotateAnim.duration
property real stiffness: 180
property real dampingRatio: 0.6
property real visibilityThreshold: 0.075
readonly property real springDuration: {
const wn = Math.sqrt(stiffness);
const r = -dampingRatio * wn;
const c = 1 / Math.sqrt(1 - dampingRatio * dampingRatio);
return Math.log(visibilityThreshold / c) / r;
}
readonly property real springMaxVelocity: {
const wn = Math.sqrt(stiffness);
const factor = Math.exp(-z * Math.acos(z) / Math.sqrt(1 - z * z));
return wn * factor;
}
property bool springSettled: true
function spring(t: real): var {
const wn = Math.sqrt(stiffness);
const za = dampingRatio * wn;
const wd = wn * Math.sqrt(1 - dampingRatio * dampingRatio);
const r = za / wd;
const pos = 1 - Math.exp(-za * t) * (Math.cos(wd * t) + r * Math.sin(wd * t));
const vel = Math.exp(-za * t) * (wn * wn / wd) * Math.sin(wd * t);
return [pos, vel];
}
implicitSize: 38
color: Colours.palette.m3primary
toShape: shapes[0]
ElapsedTimer {
id: timer
}
FrameAnimation {
running: root.animated && !root.springSettled
onTriggered: {
const t = timer.elapsed();
if (t >= root.springDuration) {
root.springSettled = true;
} else {
const [pos, vel] = root.spring(t);
root.morphProgress = Math.min(1, pos); // Overshooting the morph looks weird
root.thisLRotation = pos * root.morphAnimRotation;
root.scale = 1 + vel * root.morphScale / root.springMaxVelocity;
}
}
}
Timer {
interval: 650
repeat: true
triggeredOnStart: true
running: root.animated
onTriggered: {
root.beginBatchUpdate();
root.fromShape = root.toShape;
root.shapeIndex = (root.shapeIndex + 1) % root.shapes.length;
root.toShape = root.shapes[root.shapeIndex];
root.morphProgress = 0;
root.rotation = root.rotation;
root.lRotation = (root.lRotation + root.thisLRotation) % 360;
root.thisLRotation = 0;
root.rotation = Qt.binding(() => root.cRotation + root.lRotation + root.thisLRotation);
root.springSettled = false;
timer.restart();
root.endBatchUpdate();
}
}
RotationAnimation on cRotation {
id: rotateAnim
running: root.animated
from: 0
to: 360
easing.type: Easing.Linear
loops: Animation.Infinite
duration: 4666
}
Behavior on color {
CAnim {}
}
}

View file

@ -48,7 +48,7 @@ MouseArea {
Behavior on opacity { Behavior on opacity {
Anim { Anim {
duration: Tokens.anim.durations.small type: Anim.DefaultEffects
} }
} }
@ -79,7 +79,7 @@ MouseArea {
return item.mapToItem(root.parent, 0, off).y + root.marginY; return item.mapToItem(root.parent, 0, off).y + root.marginY;
} }
radius: Tokens.rounding.normal radius: Tokens.rounding.large
level: 2 level: 2
implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2) implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2)
@ -90,9 +90,7 @@ MouseArea {
origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0 origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0
Behavior on yScale { Behavior on yScale {
Anim { Anim {}
type: Anim.DefaultSpatial
}
} }
} }
@ -105,7 +103,7 @@ MouseArea {
id: column id: column
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.extraSmall
spacing: 0 spacing: 0
Repeater { Repeater {
@ -118,17 +116,17 @@ MouseArea {
required property int index required property int index
required property MenuItem modelData required property MenuItem modelData
readonly property bool active: modelData === root.active readonly property bool active: modelData === root?.active
Layout.fillWidth: true Layout.fillWidth: true
implicitWidth: menuOptionRow.implicitWidth + Tokens.padding.normal * 2 implicitWidth: menuOptionRow.implicitWidth + Tokens.padding.medium * 2
implicitHeight: menuOptionRow.implicitHeight + Tokens.padding.normal * 2 implicitHeight: menuOptionRow.implicitHeight + Tokens.padding.medium * 2
radius: active ? 12 : Tokens.rounding.extraSmall // This should use a token, but tokens are currently extremely scuffed radius: active ? Tokens.rounding.medium : Tokens.rounding.extraSmall
topLeftRadius: index === 0 ? Tokens.rounding.small : radius topLeftRadius: index === 0 ? Tokens.rounding.medium : radius
topRightRadius: index === 0 ? Tokens.rounding.small : radius topRightRadius: index === 0 ? Tokens.rounding.medium : radius
bottomLeftRadius: index === repeater.count - 1 ? Tokens.rounding.small : radius bottomLeftRadius: index === repeater?.count - 1 ? Tokens.rounding.medium : radius
bottomRightRadius: index === repeater.count - 1 ? Tokens.rounding.small : radius bottomRightRadius: index === repeater?.count - 1 ? Tokens.rounding.medium : radius
color: Qt.alpha(Colours.palette.m3tertiaryContainer, active ? 1 : 0) color: Qt.alpha(Colours.palette.m3tertiaryContainer, active ? 1 : 0)
@ -156,26 +154,26 @@ MouseArea {
id: menuOptionRow id: menuOptionRow
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
text: item.modelData.icon text: item.modelData?.icon ?? ""
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
text: item.modelData.text text: item.modelData?.text ?? ""
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurface color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurface
} }
Loader { Loader {
asynchronous: true asynchronous: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0 active: item.modelData?.trailingIcon.length > 0
visible: active visible: active
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {

View file

@ -15,8 +15,8 @@ StyledRect {
property var onValueModified: function (value) {} property var onValueModified: function (value) {}
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: row.implicitHeight + Tokens.padding.large * 2 implicitHeight: row.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2) color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight { Behavior on implicitHeight {
@ -30,7 +30,7 @@ StyledRect {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -12,43 +12,46 @@ Row {
Tonal Tonal
} }
property real horizontalPadding: Tokens.padding.normal property real horizontalPadding: Tokens.padding.medium
property real verticalPadding: Tokens.padding.smaller property real verticalPadding: Tokens.padding.small
property int type: SplitButton.Filled property int type: SplitButton.Filled
property bool disabled property bool disabled
property bool menuOnTop property bool menuOnTop
property string fallbackIcon property string fallbackIcon
property string fallbackText property string fallbackText
property real minLeftWidth
property alias menuItems: menu.items property alias menuItems: menu.items
property alias active: menu.active property alias active: menu.active
property alias expanded: menu.expanded property alias expanded: menu.expanded
property alias menu: menu readonly property alias menu: menu
property alias iconLabel: iconLabel readonly property alias iconLabel: iconLabel
property alias label: label readonly property alias label: label
property alias stateLayer: stateLayer readonly property alias stateLayer: stateLayer
readonly property alias textRow: textRow
readonly property alias expandBtn: expandBtn
property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1) property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
property color disabledTextColour: Qt.alpha(Colours.palette.m3onSurface, 0.38) property color disabledTextColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
spacing: Math.floor(Tokens.spacing.small / 2) spacing: Math.floor(Tokens.spacing.extraSmall)
StyledRect { StyledRect {
radius: implicitHeight / 2 * Math.min(1, Tokens.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Tokens.rounding.scale)
topRightRadius: Tokens.rounding.small / 2 topRightRadius: Tokens.rounding.medium / 2
bottomRightRadius: Tokens.rounding.small / 2 bottomRightRadius: Tokens.rounding.medium / 2
color: root.disabled ? root.disabledColour : root.colour color: root.disabled ? root.disabledColour : root.colour
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2 implicitWidth: Math.max(root.minLeftWidth, textRow.implicitWidth + root.horizontalPadding * 2)
implicitHeight: expandBtn.implicitHeight implicitHeight: expandBtn.implicitHeight
StateLayer { StateLayer {
id: stateLayer id: stateLayer
rect.topRightRadius: parent.topRightRadius topRightRadius: parent.topRightRadius
rect.bottomRightRadius: parent.bottomRightRadius bottomRightRadius: parent.bottomRightRadius
color: root.textColour color: root.textColour
disabled: root.disabled disabled: root.disabled
onClicked: root.active?.clicked() onClicked: root.active?.clicked()
@ -93,7 +96,7 @@ Row {
StyledRect { StyledRect {
id: expandBtn id: expandBtn
property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Tokens.rounding.scale) : Tokens.rounding.small / 2 property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Tokens.rounding.scale) : Tokens.rounding.medium / 2
radius: implicitHeight / 2 * Math.min(1, Tokens.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Tokens.rounding.scale)
topLeftRadius: rad topLeftRadius: rad

View file

@ -20,8 +20,8 @@ StyledRect {
signal selected(item: MenuItem) signal selected(item: MenuItem)
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: row.implicitHeight + Tokens.padding.large * 2 implicitHeight: row.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2) color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
clip: false clip: false
@ -33,7 +33,7 @@ StyledRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.large anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -21,14 +21,14 @@ Item {
signal editingFinished signal editingFinished
implicitWidth: 70 implicitWidth: 70
implicitHeight: inputField.implicitHeight + Tokens.padding.small * 2 implicitHeight: inputField.implicitHeight + Tokens.padding.small
StyledRect { StyledRect {
id: container id: container
anchors.fill: parent anchors.fill: parent
color: inputHover.containsMouse || inputField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2) color: inputHover.containsMouse || inputField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.small radius: Tokens.rounding.medium
border.width: 1 border.width: 1
border.color: inputField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3) border.color: inputField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
opacity: root.enabled ? 1 : 0.5 opacity: root.enabled ? 1 : 0.5
@ -54,7 +54,7 @@ Item {
id: inputField id: inputField
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Tokens.padding.normal width: parent.width - Tokens.padding.medium
horizontalAlignment: root.horizontalAlignment horizontalAlignment: root.horizontalAlignment
validator: root.validator validator: root.validator
readOnly: root.readOnly readOnly: root.readOnly

View file

@ -0,0 +1,247 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Templates
import Caelestia
import Caelestia.Components
import Caelestia.Config
import Caelestia.Internal
import qs.components
import qs.services
ProgressBar {
id: root
enum IndeterminateAnimState {
Running,
Completing,
Stopped
}
property color fgColour: Colours.palette.m3primary
property color bgColour: Colours.palette.m3secondaryContainer
property bool wavy
property bool wavePaused
property int waveFrequency: 6
property real waveAmplitude: 0.5
property int waveDuration: 1000
property int indeterminateAnimState: StyledProgressBar.Stopped
function toBounds(startFrac: real, endFrac: real, gapSize: real): point {
startFrac = CUtils.clamp(startFrac, 0, 1);
endFrac = CUtils.clamp(endFrac, 0, 1);
// Ramp down gap size
const GAP_RAMP_DOWN_THRESHOLD = 0.01;
gapSize += height / 2;
const startGapSize = (gapSize * CUtils.clamp(startFrac, 0, GAP_RAMP_DOWN_THRESHOLD) / GAP_RAMP_DOWN_THRESHOLD);
const endGapSize = (gapSize * (1 - CUtils.clamp(endFrac, 1 - GAP_RAMP_DOWN_THRESHOLD, 1)) / GAP_RAMP_DOWN_THRESHOLD);
const start = width * startFrac + startGapSize;
const end = width * endFrac - endGapSize;
return start >= end ? Qt.point(0, 0) : Qt.point(start, end);
}
function updateIAnimState(): void {
if (indeterminate) {
manager.completeEndProgress = 0;
indeterminateAnimState = StyledProgressBar.Running;
} else if (indeterminateAnimState === StyledProgressBar.Running) {
indeterminateAnimState = StyledProgressBar.Completing;
}
}
onIndeterminateChanged: updateIAnimState()
Component.onCompleted: updateIAnimState()
implicitWidth: 200
implicitHeight: 4
contentItem: Loader {
anchors.fill: parent
asynchronous: true
sourceComponent: root.indeterminate || root.indeterminateAnimState !== StyledProgressBar.Stopped ? indeterminateComp : determinateComp
}
LinearIndicatorManager {
id: manager
gap: root.Tokens.spacing.extraSmall
Anim on progress {
running: root.indeterminateAnimState !== StyledProgressBar.Stopped
from: 0
to: 1
duration: manager.duration
easing.type: Easing.Linear
loops: Animation.Infinite
}
Anim on completeEndProgress {
running: root.indeterminateAnimState === StyledProgressBar.Completing
to: 1
duration: manager.completeEndDuration
onFinished: {
if (root.indeterminateAnimState === StyledProgressBar.Completing)
root.indeterminateAnimState = StyledProgressBar.Stopped;
}
}
}
Component {
id: determinateComp
Item {
Line {
id: remaining
anchors.right: parent.right
implicitWidth: parent.width - wave.implicitWidth - Tokens.spacing.extraSmall
}
Line {
property real implicitSize
Component.onCompleted: implicitSize = Qt.binding(() => parent.width - wave.implicitWidth < parent.height ? parent.height : 4)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: (parent.height - implicitHeight) / 2
implicitWidth: implicitSize
implicitHeight: implicitSize
radius: Tokens.rounding.full
color: root.fgColour
Behavior on implicitSize {
Anim {
type: Anim.FastSpatial
}
}
}
Wave {
id: wave
anchors.left: parent.left
Component.onCompleted: implicitWidth = Qt.binding(() => parent.width * root.visualPosition)
Behavior on implicitWidth {
Anim {}
}
}
}
}
Component {
id: indeterminateComp
Item {
id: content
Line {
bounds: {
const i = manager.activeIndicators[0]; // qmllint disable unresolved-type
return i ? root.toBounds(0, i.startFraction, i.gapSize / 2) : Qt.point(0, 0);
}
}
Line {
bounds: {
const i = manager.activeIndicators[manager.activeIndicators.length - 1]; // qmllint disable unresolved-type
return i ? root.toBounds(i.endFraction, 1, i.gapSize / 2) : Qt.point(0, 0);
}
}
Instantiator {
model: Math.max(manager.activeIndicators.length, 1) - 1 // qmllint disable unresolved-type
delegate: Line {
required property int index
readonly property LinearIndicatorSegment cur: manager.activeIndicators[index] // qmllint disable unresolved-type
readonly property LinearIndicatorSegment next: manager.activeIndicators[index + 1 % manager.activeIndicators.length] // qmllint disable unresolved-type
bounds: root.toBounds(cur.endFraction, next.startFraction, cur.gapSize / 2)
}
onObjectAdded: (_, obj) => content.data.push(obj)
onObjectRemoved: (_, obj) => {
const idx = content.data.indexOf(obj);
if (idx !== -1)
content.data.splice(idx, 1);
}
}
Instantiator {
model: manager.activeIndicators // qmllint disable unresolved-type
delegate: Wave {
required property LinearIndicatorSegment modelData
readonly property point bounds: root.toBounds(modelData.startFraction, modelData.endFraction, modelData.gapSize / 2)
x: bounds.x
implicitWidth: bounds.y - bounds.x
color: root.fgColour
}
onObjectAdded: (_, obj) => content.data.push(obj)
onObjectRemoved: (_, obj) => {
const idx = content.data.indexOf(obj);
if (idx !== -1)
content.data.splice(idx, 1);
}
}
}
}
component Line: StyledRect {
property point bounds
x: bounds.x
implicitWidth: bounds.y - bounds.x
anchors.verticalCenter: parent.verticalCenter
implicitHeight: parent.height
radius: Tokens.rounding.full
color: root.bgColour
}
component Wave: WavyLine {
id: wave
anchors.verticalCenter: parent.verticalCenter
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
lineWidth: parent.height
amplitudeMultiplier: root.wavy ? root.waveAmplitude : 0
frequency: root.waveFrequency
startX: x
fullLength: parent.width
color: root.fgColour
Anim on waveProgress {
running: true
paused: wave.amplitudeMultiplier === 0 || root.wavePaused
from: 0
to: 1
duration: root.waveDuration
easing.type: Easing.Linear
loops: Animation.Infinite
}
Behavior on amplitudeMultiplier {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on color {
CAnim {}
}
}
}

View file

@ -7,7 +7,7 @@ import qs.services
RadioButton { RadioButton {
id: root id: root
font.pointSize: Tokens.font.size.smaller font: Tokens.font.body.small
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight) implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
@ -24,7 +24,7 @@ RadioButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StateLayer { StateLayer {
anchors.margins: -Tokens.padding.smaller anchors.margins: -Tokens.padding.small
color: root.checked ? Colours.palette.m3onSurface : Colours.palette.m3primary color: root.checked ? Colours.palette.m3onSurface : Colours.palette.m3primary
z: -1 z: -1
onClicked: root.click() onClicked: root.click()
@ -46,9 +46,9 @@ RadioButton {
contentItem: StyledText { contentItem: StyledText {
text: root.text text: root.text
font.pointSize: root.font.pointSize font: root.font
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: outerCircle.right anchors.left: outerCircle.right
anchors.leftMargin: Tokens.spacing.smaller anchors.leftMargin: Tokens.spacing.medium
} }
} }

View file

@ -45,7 +45,7 @@ ScrollBar {
} }
} }
} }
implicitWidth: Tokens.padding.small implicitWidth: Tokens.padding.extraSmall
contentItem: StyledRect { contentItem: StyledRect {
anchors.left: parent.left anchors.left: parent.left
@ -74,7 +74,9 @@ ScrollBar {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }

View file

@ -1,5 +1,9 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import Caelestia
import Caelestia.Components
import Caelestia.Config import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
@ -7,51 +11,178 @@ import qs.services
Slider { Slider {
id: root id: root
background: Item { property bool wavy
StyledRect { property bool animateWave
anchors.top: parent.top property real waveFrequency: 6
anchors.bottom: parent.bottom property int waveDuration: 1000
anchors.left: parent.left property int radius: Tokens.rounding.medium
anchors.topMargin: root.implicitHeight / 3
anchors.bottomMargin: root.implicitHeight / 3
implicitWidth: root.handle.x - root.implicitHeight / 6 property color fgColour: enabled ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3onSurface, 0.38)
property color bgColour: enabled ? Colours.palette.m3secondaryContainer : Qt.alpha(Colours.palette.m3onSurface, 0.1)
color: Colours.palette.m3primary property real pos: visualPosition
radius: Tokens.rounding.full property real filledWidth
topRightRadius: root.implicitHeight / 15
bottomRightRadius: root.implicitHeight / 15 signal interaction(v: real)
}
Component.onCompleted: filledWidth = Qt.binding(() => (width - handle.implicitWidth - handle.anchors.leftMargin) * pos)
implicitWidth: 200
implicitHeight: 12
contentItem: Item {
anchors.fill: parent
StyledRect { StyledRect {
anchors.top: parent.top id: remaining
anchors.bottom: parent.bottom
anchors.left: handle.right
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: root.implicitHeight / 3 anchors.verticalCenter: parent.verticalCenter
anchors.bottomMargin: root.implicitHeight / 3 anchors.leftMargin: Tokens.spacing.extraSmall
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 6 implicitHeight: parent.height * (parent.height <= 12 ? opacity : Math.min(opacity * 2, 1))
opacity: Math.min(width, 12) / 12
radius: root.radius
topLeftRadius: Tokens.rounding.extraSmall / 2
bottomLeftRadius: Tokens.rounding.extraSmall / 2
color: root.bgColour
}
StyledRect {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 4 * remaining.opacity
implicitWidth: implicitHeight
implicitHeight: 4 * remaining.opacity
opacity: remaining.opacity
color: Colours.palette.m3surfaceContainerHighest
radius: Tokens.rounding.full radius: Tokens.rounding.full
topLeftRadius: root.implicitHeight / 15 color: root.fgColour
bottomLeftRadius: root.implicitHeight / 15 }
StyledRect {
id: handle
anchors.left: filled.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.spacing.extraSmall
implicitWidth: 4
implicitHeight: {
const t = CUtils.clamp((parent.height - 12) / 16, 0, 1);
const lerp = (a, b) => a + (b - a) * t;
return parent.height * (mouse.pressed ? lerp(3.5, 1.5) : lerp(3, 1.2));
}
radius: Tokens.rounding.full
color: root.fgColour
Behavior on implicitHeight {
Anim {
type: Anim.FastSpatial
}
} }
} }
handle: StyledRect { Loader {
x: root.visualPosition * root.availableWidth - implicitWidth / 2 id: filled
implicitWidth: root.implicitHeight / 4.5 anchors.left: parent.left
implicitHeight: root.implicitHeight anchors.verticalCenter: parent.verticalCenter
asynchronous: true
color: Colours.palette.m3primary sourceComponent: root.wavy ? waveComp : lineComp
radius: Tokens.rounding.full }
Component {
id: lineComp
StyledRect {
implicitWidth: root.filledWidth
implicitHeight: root.height
radius: root.radius
topRightRadius: Tokens.rounding.extraSmall / 2
bottomRightRadius: Tokens.rounding.extraSmall / 2
color: root.fgColour
}
}
Component {
id: waveComp
WavyLine {
lineWidth: root.height * 0.7
frequency: root.waveFrequency
startX: x
fullLength: root.width - handle.implicitWidth - handle.anchors.leftMargin
color: root.fgColour
implicitWidth: root.filledWidth
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
Anim on waveProgress {
running: true
paused: !root.animateWave
from: 0
to: 1
duration: root.waveDuration
easing.type: Easing.Linear
loops: Animation.Infinite
}
Behavior on color {
CAnim {}
}
}
}
}
Binding {
id: posBinding
target: root
property: "pos"
value: CUtils.clamp(mouse.pressStartPos + mouse.dragMovement, 0, 1)
when: mouse.pressed
}
MouseArea { MouseArea {
anchors.fill: parent id: mouse
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor property real pressStartX
property real pressStartPos
property real dragMovement
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
preventStealing: true
implicitHeight: handle.implicitHeight
onPressed: e => {
widthBehavior.enabled = false;
pressStartX = e.x;
pressStartPos = root.visualPosition;
} }
onPositionChanged: e => {
dragMovement = (e.x - pressStartX) / width;
root.interaction(posBinding.value);
}
onReleased: e => {
root.interaction(posBinding.value);
widthBehavior.enabled = true;
dragMovement = 0;
}
}
Behavior on filledWidth {
id: widthBehavior
Anim {}
} }
} }

View file

@ -18,17 +18,17 @@ Switch {
color: root.checked ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHighest, root.cLayer) color: root.checked ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHighest, root.cLayer)
implicitWidth: implicitHeight * 1.7 implicitWidth: implicitHeight * 1.7
implicitHeight: Tokens.font.size.normal + Tokens.padding.smaller * 2 implicitHeight: Tokens.font.body.medium.pointSize + Tokens.padding.small * 2
StyledRect { StyledRect {
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.2 : implicitHeight
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: root.checked ? Colours.palette.m3onPrimary : Colours.layer(Colours.palette.m3outline, root.cLayer + 1) color: root.checked ? Colours.palette.m3onPrimary : Colours.layer(Colours.palette.m3outline, root.cLayer + 1)
x: root.checked ? parent.implicitWidth - nonAnimWidth - Tokens.padding.small / 2 : Tokens.padding.small / 2 x: root.checked ? parent.implicitWidth - nonAnimWidth - Tokens.padding.extraSmall / 2 : Tokens.padding.extraSmall / 2
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
implicitHeight: parent.implicitHeight - Tokens.padding.small implicitHeight: parent.implicitHeight - Tokens.padding.extraSmall
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledRect { StyledRect {
@ -39,7 +39,9 @@ Switch {
opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0 opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -83,12 +85,12 @@ Switch {
anchors.centerIn: parent anchors.centerIn: parent
width: height width: height
height: parent.implicitHeight - Tokens.padding.small * 2 height: parent.implicitHeight - Tokens.padding.medium
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
asynchronous: true asynchronous: true
ShapePath { ShapePath {
strokeWidth: root.Tokens.font.size.larger * 0.15 strokeWidth: root.Tokens.font.body.large.pointSize * 0.15
strokeColor: root.checked ? Colours.palette.m3primary : Colours.palette.m3surfaceContainerHighest strokeColor: root.checked ? Colours.palette.m3primary : Colours.palette.m3surfaceContainerHighest
fillColor: "transparent" fillColor: "transparent"
capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
@ -129,11 +131,15 @@ Switch {
} }
Behavior on x { Behavior on x {
Anim {} Anim {
type: Anim.FastSpatial
}
} }
Behavior on implicitWidth { Behavior on implicitWidth {
Anim {} Anim {
type: Anim.FastSpatial
}
} }
} }
} }
@ -145,7 +151,7 @@ Switch {
} }
component PropAnim: PropertyAnimation { component PropAnim: PropertyAnimation {
duration: Tokens.anim.durations.normal duration: Tokens.anim.durations.expressiveFastSpatial
easing: Tokens.anim.standard easing: Tokens.anim.expressiveFastSpatial
} }
} }

View file

@ -11,8 +11,7 @@ TextField {
color: Colours.palette.m3onSurface color: Colours.palette.m3onSurface
placeholderTextColor: Colours.palette.m3outline placeholderTextColor: Colours.palette.m3outline
font.family: Tokens.font.family.sans font: Tokens.font.body.small
font.pointSize: Tokens.font.size.smaller
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
cursorVisible: !readOnly cursorVisible: !readOnly
@ -25,7 +24,7 @@ TextField {
implicitWidth: 2 implicitWidth: 2
color: Colours.palette.m3primary color: Colours.palette.m3primary
radius: Tokens.rounding.normal radius: Tokens.rounding.large
Connections { Connections {
function onCursorPositionChanged(): void { function onCursorPositionChanged(): void {

View file

@ -13,8 +13,8 @@ StyledRect {
signal toggled(checked: bool) signal toggled(checked: bool)
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: row.implicitHeight + Tokens.padding.large * 2 implicitHeight: row.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2) color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight { Behavior on implicitHeight {
@ -28,7 +28,7 @@ StyledRect {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -3,75 +3,41 @@ import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
StyledRect { ButtonBase {
id: root id: root
enum Type {
Filled,
Tonal,
Text
}
property alias text: label.text property alias text: label.text
property bool checked readonly property alias label: label
property bool toggle
property real horizontalPadding: Tokens.padding.normal
property real verticalPadding: Tokens.padding.smaller
property alias font: label.font
property int type: TextButton.Filled
property alias stateLayer: stateLayer horizontalPadding: Tokens.padding.medium
property alias label: label verticalPadding: Tokens.padding.small
property bool internalChecked activeColour: type === TextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
property color activeColour: type === TextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary inactiveColour: {
property color inactiveColour: { if (!isToggle && type === TextButton.Filled)
if (!toggle && type === TextButton.Filled)
return Colours.palette.m3primary; return Colours.palette.m3primary;
return type === TextButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer; return type === TextButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer;
} }
property color activeOnColour: { activeOnColour: {
if (type === TextButton.Text) if (type === TextButton.Text)
return Colours.palette.m3primary; return Colours.palette.m3primary;
return type === TextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary; return type === TextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary;
} }
property color inactiveOnColour: { inactiveOnColour: {
if (!toggle && type === TextButton.Filled) if (!isToggle && type === TextButton.Filled)
return Colours.palette.m3onPrimary; return Colours.palette.m3onPrimary;
if (type === TextButton.Text) if (type === TextButton.Text)
return Colours.palette.m3primary; return Colours.palette.m3primary;
return type === TextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer; return type === TextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer;
} }
signal clicked
onCheckedChanged: internalChecked = checked
radius: internalChecked ? Tokens.rounding.small : implicitHeight / 2 * Math.min(1, Tokens.rounding.scale)
color: type === TextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
implicitWidth: label.implicitWidth + horizontalPadding * 2 implicitWidth: label.implicitWidth + horizontalPadding * 2
implicitHeight: label.implicitHeight + verticalPadding * 2 implicitHeight: label.implicitHeight + verticalPadding * 2
StateLayer {
id: stateLayer
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
onClicked: {
if (root.toggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
}
StyledText { StyledText {
id: label id: label
anchors.centerIn: parent anchors.centerIn: parent
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour color: root.onColour
}
Behavior on radius {
Anim {}
} }
} }

View file

@ -14,9 +14,9 @@ StyledRect {
property string icon property string icon
property string label property string label
property string accent: "Secondary" property string accent: "Secondary"
property real iconSize: Tokens.font.size.large property real iconSize: Tokens.font.icon.large.pointSize
property real horizontalPadding: Tokens.padding.large property real horizontalPadding: Tokens.padding.large
property real verticalPadding: Tokens.padding.normal property real verticalPadding: Tokens.padding.medium
property string tooltip: "" property string tooltip: ""
property bool hovered: false property bool hovered: false
@ -24,10 +24,10 @@ StyledRect {
Component.onCompleted: hovered = toggleStateLayer.containsMouse Component.onCompleted: hovered = toggleStateLayer.containsMouse
Layout.preferredWidth: implicitWidth + (toggleStateLayer.pressed ? Tokens.padding.normal * 2 : toggled ? Tokens.padding.small * 2 : 0) Layout.preferredWidth: implicitWidth + (toggleStateLayer.pressed ? Tokens.padding.medium * 2 : toggled ? Tokens.padding.small : 0)
implicitWidth: toggleBtnInner.implicitWidth + horizontalPadding * 2 implicitWidth: toggleBtnInner.implicitWidth + horizontalPadding * 2
implicitHeight: toggleBtnIcon.implicitHeight + verticalPadding * 2 implicitHeight: toggleBtnIcon.implicitHeight + verticalPadding * 2
radius: toggled || toggleStateLayer.pressed ? Tokens.rounding.small : Math.min(width, height) / 2 * Math.min(1, Tokens.rounding.scale) radius: toggled || toggleStateLayer.pressed ? Tokens.rounding.medium : Math.min(width, height) / 2 * Math.min(1, Tokens.rounding.scale)
color: toggled ? Colours.palette[`m3${accent.toLowerCase()}`] : Colours.palette[`m3${accent.toLowerCase()}Container`] color: toggled ? Colours.palette[`m3${accent.toLowerCase()}`] : Colours.palette[`m3${accent.toLowerCase()}Container`]
Connections { Connections {
@ -52,7 +52,7 @@ StyledRect {
id: toggleBtnInner id: toggleBtnInner
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
MaterialIcon { MaterialIcon {
id: toggleBtnIcon id: toggleBtnIcon
@ -61,10 +61,12 @@ StyledRect {
fill: root.toggled ? 1 : 0 fill: root.toggled ? 1 : 0
text: root.icon text: root.icon
color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`] color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
font.pointSize: root.iconSize fontStyle: Tokens.font.icon.size(root.iconSize).build()
Behavior on fill { Behavior on fill {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }

View file

@ -12,7 +12,7 @@ RowLayout {
property alias toggle: toggle property alias toggle: toggle
Layout.fillWidth: true Layout.fillWidth: true
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -47,7 +47,7 @@ Popup {
let newY = targetPos.y - tooltipHeight - Tokens.spacing.small; let newY = targetPos.y - tooltipHeight - Tokens.spacing.small;
// Keep within bounds // Keep within bounds
const padding = Tokens.padding.normal; const padding = Tokens.padding.medium;
if (newX < padding) { if (newX < padding) {
newX = padding; newX = padding;
} else if (newX + tooltipWidth > (parent.width - padding)) { } else if (newX + tooltipWidth > (parent.width - padding)) {
@ -116,11 +116,11 @@ Popup {
contentItem: StyledRect { contentItem: StyledRect {
id: tooltipRect id: tooltipRect
implicitWidth: tooltipText.implicitWidth + Tokens.padding.normal * 2 implicitWidth: tooltipText.implicitWidth + Tokens.padding.medium * 2
implicitHeight: tooltipText.implicitHeight + Tokens.padding.smaller * 2 implicitHeight: tooltipText.implicitHeight + Tokens.padding.large
color: Colours.palette.m3surfaceContainerHighest color: Colours.palette.m3surfaceContainerHighest
radius: Tokens.rounding.small radius: Tokens.rounding.medium
antialiasing: true antialiasing: true
// Add elevation for depth // Add elevation for depth
@ -138,7 +138,7 @@ Popup {
text: root.text text: root.text
color: Colours.palette.m3onSurface color: Colours.palette.m3onSurface
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
} }

View file

@ -13,6 +13,8 @@ RectangularShadow {
offset.y: dp / 2 offset.y: dp / 2
Behavior on dp { Behavior on dp {
Anim {} Anim {
type: Anim.SlowEffects
}
} }
} }

View file

@ -37,8 +37,8 @@ StyledRect {
id: maskInner id: maskInner
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
radius: Tokens.rounding.normal radius: Tokens.rounding.large
} }
} }
} }

View file

@ -0,0 +1,7 @@
import QtQuick.Effects
MultiEffect {
maskEnabled: true
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}

View file

@ -1,9 +0,0 @@
import QtQuick
import Quickshell
ShaderEffect {
required property Item source
required property Item maskSource
fragmentShader: Quickshell.shellPath("assets/shaders/opacitymask.frag.qsb")
}

View file

@ -9,8 +9,8 @@ Item {
required property var currentItem required property var currentItem
implicitWidth: content.implicitWidth + Tokens.padding.larger + content.anchors.rightMargin implicitWidth: content.implicitWidth + Tokens.padding.medium + content.anchors.rightMargin
implicitHeight: currentItem ? content.implicitHeight + Tokens.padding.normal + content.anchors.bottomMargin : 0 implicitHeight: currentItem ? content.implicitHeight + Tokens.padding.medium + content.anchors.bottomMargin : 0
Shape { Shape {
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
@ -18,7 +18,7 @@ Item {
ShapePath { ShapePath {
id: path id: path
readonly property real rounding: Tokens.rounding.small readonly property real rounding: root.Tokens.rounding.medium
readonly property bool flatten: root.implicitHeight < rounding * 2 readonly property bool flatten: root.implicitHeight < rounding * 2
readonly property real roundingY: flatten ? root.implicitHeight / 2 : rounding readonly property real roundingY: flatten ? root.implicitHeight / 2 : rounding
@ -76,8 +76,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.rightMargin: Tokens.padding.larger - Tokens.padding.small anchors.rightMargin: Tokens.padding.medium - Tokens.padding.extraSmall
anchors.bottomMargin: Tokens.padding.normal - Tokens.padding.small anchors.bottomMargin: Tokens.padding.medium - Tokens.padding.extraSmall
Connections { Connections {
function onCurrentItemChanged(): void { function onCurrentItemChanged(): void {

View file

@ -9,7 +9,7 @@ StyledRect {
required property var dialog required property var dialog
required property FolderContents folder required property FolderContents folder
implicitHeight: inner.implicitHeight + Tokens.padding.normal * 2 implicitHeight: inner.implicitHeight + Tokens.padding.medium * 2
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
@ -17,7 +17,7 @@ StyledRect {
id: inner id: inner
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
@ -28,14 +28,14 @@ StyledRect {
StyledRect { StyledRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.rightMargin: Tokens.spacing.normal Layout.rightMargin: Tokens.spacing.medium
color: Colours.tPalette.m3surfaceContainerHigh color: Colours.tPalette.m3surfaceContainerHigh
radius: Tokens.rounding.small radius: Tokens.rounding.medium
StyledText { StyledText {
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
text: `${root.dialog.filterLabel} (${root.dialog.filters.map(f => `*.${f}`).join(", ")})` text: `${root.dialog.filterLabel} (${root.dialog.filters.map(f => `*.${f}`).join(", ")})`
} }
@ -43,10 +43,10 @@ StyledRect {
StyledRect { StyledRect {
color: Colours.tPalette.m3surfaceContainerHigh color: Colours.tPalette.m3surfaceContainerHigh
radius: Tokens.rounding.small radius: Tokens.rounding.medium
implicitWidth: cancelText.implicitWidth + Tokens.padding.normal * 2 implicitWidth: cancelText.implicitWidth + Tokens.padding.medium * 2
implicitHeight: cancelText.implicitHeight + Tokens.padding.normal * 2 implicitHeight: cancelText.implicitHeight + Tokens.padding.medium * 2
StateLayer { StateLayer {
disabled: !root.dialog.selectionValid disabled: !root.dialog.selectionValid
@ -57,7 +57,7 @@ StyledRect {
id: selectText id: selectText
anchors.centerIn: parent anchors.centerIn: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
text: qsTr("Select") text: qsTr("Select")
color: root.dialog.selectionValid ? Colours.palette.m3onSurface : Colours.palette.m3outline color: root.dialog.selectionValid ? Colours.palette.m3onSurface : Colours.palette.m3outline
@ -66,10 +66,10 @@ StyledRect {
StyledRect { StyledRect {
color: Colours.tPalette.m3surfaceContainerHigh color: Colours.tPalette.m3surfaceContainerHigh
radius: Tokens.rounding.small radius: Tokens.rounding.medium
implicitWidth: cancelText.implicitWidth + Tokens.padding.normal * 2 implicitWidth: cancelText.implicitWidth + Tokens.padding.medium * 2
implicitHeight: cancelText.implicitHeight + Tokens.padding.normal * 2 implicitHeight: cancelText.implicitHeight + Tokens.padding.medium * 2
StateLayer { StateLayer {
onClicked: { onClicked: {
@ -81,7 +81,7 @@ StyledRect {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
text: qsTr("Cancel") text: qsTr("Cancel")
} }

View file

@ -50,7 +50,10 @@ LazyLoader {
implicitWidth: 1000 implicitWidth: 1000
implicitHeight: 600 implicitHeight: 600
minimumSize.width: 400
minimumSize.height: 300
color: Colours.tPalette.m3surface color: Colours.tPalette.m3surface
surfaceFormat.opaque: false
title: loader.title title: loader.title
onVisibleChanged: { onVisibleChanged: {

View file

@ -1,13 +1,13 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Caelestia.Config import Caelestia.Config
import Caelestia.Models import Caelestia.Models
import qs.components import qs.components
import qs.components.controls import qs.components.controls
import qs.components.effects
import qs.components.filedialog import qs.components.filedialog
import qs.components.images import qs.components.images
import qs.services import qs.services
@ -24,12 +24,9 @@ Item {
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: Mask {
maskSource: mask maskSource: mask
maskEnabled: true
maskInverted: true maskInverted: true
maskThresholdMin: 0.5
maskSpreadAtMin: 1
} }
} }
@ -42,8 +39,8 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.extraSmall
radius: Tokens.rounding.small radius: Tokens.rounding.medium
} }
} }
@ -59,20 +56,20 @@ Item {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "scan_delete" text: "scan_delete"
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.extraLarge * 2 fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).weight(Font.Medium).build()
font.weight: 500
} }
StyledText { StyledText {
text: qsTr("This folder is empty") text: qsTr("This folder is empty")
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.large font: Tokens.font.body.builders.large.weight(Font.Medium).build()
font.weight: 500
} }
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -80,10 +77,10 @@ Item {
id: view id: view
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.small + Tokens.padding.normal anchors.margins: Tokens.padding.extraSmall + Tokens.padding.medium
cellWidth: Sizes.itemWidth + Tokens.spacing.small cellWidth: Sizes.itemWidth + Tokens.spacing.small
cellHeight: Sizes.itemWidth + Tokens.spacing.small * 2 + Tokens.padding.normal * 2 + 1 cellHeight: Sizes.itemWidth + Tokens.spacing.large + Tokens.padding.medium * 2 + 1
clip: true clip: true
focus: true focus: true
@ -120,12 +117,12 @@ Item {
properties: "opacity,scale" properties: "opacity,scale"
from: 0 from: 0
to: 1 to: 1
type: Anim.DefaultSpatial
} }
} }
remove: Transition { remove: Transition {
Anim { Anim {
type: Anim.DefaultEffects
property: "opacity" property: "opacity"
to: 0 to: 0
} }
@ -137,13 +134,13 @@ Item {
displaced: Transition { displaced: Transition {
Anim { Anim {
type: Anim.DefaultEffects
properties: "opacity,scale" properties: "opacity,scale"
to: 1 to: 1
easing: Tokens.anim.standardDecel easing: Tokens.anim.standardDecel
} }
Anim { Anim {
properties: "x,y" properties: "x,y"
type: Anim.DefaultSpatial
} }
} }
} }
@ -151,7 +148,7 @@ Item {
CurrentItem { CurrentItem {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.extraSmall
currentItem: view.currentItem currentItem: view.currentItem
} }
@ -162,12 +159,12 @@ Item {
required property int index required property int index
required property FileSystemEntry modelData required property FileSystemEntry modelData
readonly property real nonAnimHeight: icon.implicitHeight + name.anchors.topMargin + name.implicitHeight + Tokens.padding.normal * 2 readonly property real nonAnimHeight: icon.implicitHeight + name.anchors.topMargin + name.implicitHeight + Tokens.padding.medium * 2
implicitWidth: Sizes.itemWidth implicitWidth: Sizes.itemWidth
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: Qt.alpha(Colours.tPalette.m3surfaceContainerHighest, GridView.isCurrentItem ? Colours.tPalette.m3surfaceContainerHighest.a : 0) color: Qt.alpha(Colours.tPalette.m3surfaceContainerHighest, GridView.isCurrentItem ? Colours.tPalette.m3surfaceContainerHighest.a : 0)
z: GridView.isCurrentItem || implicitHeight !== nonAnimHeight ? 1 : 0 z: GridView.isCurrentItem || implicitHeight !== nonAnimHeight ? 1 : 0
clip: true clip: true
@ -187,9 +184,9 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Tokens.padding.normal anchors.topMargin: Tokens.padding.medium
implicitSize: Sizes.itemWidth - Tokens.padding.normal * 2 implicitSize: Sizes.itemWidth - Tokens.padding.medium * 2
Component.onCompleted: { Component.onCompleted: {
const file = item.modelData; const file = item.modelData;
@ -211,7 +208,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: icon.bottom anchors.top: icon.bottom
anchors.topMargin: Tokens.spacing.small anchors.topMargin: Tokens.spacing.small
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
elide: item.GridView.isCurrentItem ? Text.ElideNone : Text.ElideRight elide: item.GridView.isCurrentItem ? Text.ElideNone : Text.ElideRight

View file

@ -11,8 +11,8 @@ StyledRect {
required property var dialog required property var dialog
implicitWidth: inner.implicitWidth + Tokens.padding.normal * 2 implicitWidth: inner.implicitWidth + Tokens.padding.medium * 2
implicitHeight: inner.implicitHeight + Tokens.padding.normal * 2 implicitHeight: inner.implicitHeight + Tokens.padding.medium * 2
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
@ -20,15 +20,15 @@ StyledRect {
id: inner id: inner
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
Item { Item {
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: upIcon.implicitHeight + Tokens.padding.small * 2 implicitHeight: upIcon.implicitHeight + Tokens.padding.small
StateLayer { StateLayer {
radius: Tokens.rounding.small radius: Tokens.rounding.medium
disabled: root.dialog.cwd.length === 1 disabled: root.dialog.cwd.length === 1
onClicked: root.dialog.cwd.pop() onClicked: root.dialog.cwd.pop()
} }
@ -46,7 +46,7 @@ StyledRect {
StyledRect { StyledRect {
Layout.fillWidth: true Layout.fillWidth: true
radius: Tokens.rounding.small radius: Tokens.rounding.medium
color: Colours.tPalette.m3surfaceContainerHigh color: Colours.tPalette.m3surfaceContainerHigh
implicitHeight: pathComponents.implicitHeight + pathComponents.anchors.margins * 2 implicitHeight: pathComponents.implicitHeight + pathComponents.anchors.margins * 2
@ -55,7 +55,7 @@ StyledRect {
id: pathComponents id: pathComponents
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.small / 2 anchors.margins: Tokens.padding.extraSmall / 2
anchors.leftMargin: 0 anchors.leftMargin: 0
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
@ -78,13 +78,13 @@ StyledRect {
sourceComponent: StyledText { sourceComponent: StyledText {
text: "/" text: "/"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.bold: true font: Tokens.font.body.builders.small.weight(Font.Bold).build()
} }
} }
Item { Item {
implicitWidth: homeIcon.implicitWidth + (homeIcon.active ? Tokens.padding.small : 0) + folderName.implicitWidth + Tokens.padding.normal * 2 implicitWidth: homeIcon.implicitWidth + (homeIcon.active ? Tokens.padding.extraSmall : 0) + folderName.implicitWidth + Tokens.padding.medium * 2
implicitHeight: folderName.implicitHeight + Tokens.padding.small * 2 implicitHeight: folderName.implicitHeight + Tokens.padding.small
Loader { Loader {
asynchronous: true asynchronous: true
@ -95,7 +95,7 @@ StyledRect {
root.dialog.cwd = root.dialog.cwd.slice(0, folder.index + 1); root.dialog.cwd = root.dialog.cwd.slice(0, folder.index + 1);
} }
radius: Tokens.rounding.small radius: Tokens.rounding.medium
} }
} }
@ -106,7 +106,7 @@ StyledRect {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.padding.normal anchors.leftMargin: Tokens.padding.medium
active: folder.index === 0 && folder.modelData === "Home" active: folder.index === 0 && folder.modelData === "Home"
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
@ -121,11 +121,11 @@ StyledRect {
anchors.left: homeIcon.right anchors.left: homeIcon.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: homeIcon.active ? Tokens.padding.small : 0 anchors.leftMargin: homeIcon.active ? Tokens.padding.extraSmall : 0
text: folder.modelData text: folder.modelData
color: folder.index < root.dialog.cwd.length - 1 ? Colours.palette.m3onSurfaceVariant : Colours.palette.m3onSurface color: folder.index < root.dialog.cwd.length - 1 ? Colours.palette.m3onSurfaceVariant : Colours.palette.m3onSurface
font.bold: true font: Tokens.font.body.builders.small.weight(Font.Bold).build()
} }
} }
} }

View file

@ -13,7 +13,7 @@ StyledRect {
required property var dialog required property var dialog
implicitWidth: Sizes.sidebarWidth implicitWidth: Sizes.sidebarWidth
implicitHeight: inner.implicitHeight + Tokens.padding.normal * 2 implicitHeight: inner.implicitHeight + Tokens.padding.medium * 2
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
@ -23,17 +23,16 @@ StyledRect {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.small / 2 spacing: Tokens.spacing.extraSmall
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Tokens.padding.small / 2 Layout.topMargin: Tokens.padding.extraSmall / 2
Layout.bottomMargin: Tokens.spacing.normal Layout.bottomMargin: Tokens.spacing.medium
text: qsTr("Files") text: qsTr("Files")
color: Colours.palette.m3onSurface color: Colours.palette.m3onSurface
font.pointSize: Tokens.font.size.larger font: Tokens.font.body.builders.large.weight(Font.Bold).build()
font.bold: true
} }
Repeater { Repeater {
@ -46,7 +45,7 @@ StyledRect {
readonly property bool selected: modelData === root.dialog.cwd[root.dialog.cwd.length - 1] readonly property bool selected: modelData === root.dialog.cwd[root.dialog.cwd.length - 1]
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: placeInner.implicitHeight + Tokens.padding.normal * 2 implicitHeight: placeInner.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3secondaryContainer, selected ? 1 : 0) color: Qt.alpha(Colours.palette.m3secondaryContainer, selected ? 1 : 0)
@ -65,11 +64,11 @@ StyledRect {
id: placeInner id: placeInner
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
anchors.leftMargin: Tokens.padding.large anchors.leftMargin: Tokens.padding.large
anchors.rightMargin: Tokens.padding.large anchors.rightMargin: Tokens.padding.large
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
MaterialIcon { MaterialIcon {
text: { text: {
@ -91,11 +90,13 @@ StyledRect {
return "folder"; return "folder";
} }
color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.medium
fill: place.selected ? 1 : 0 fill: place.selected ? 1 : 0
Behavior on fill { Behavior on fill {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -103,7 +104,7 @@ StyledRect {
Layout.fillWidth: true Layout.fillWidth: true
text: place.modelData text: place.modelData
color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
font.pointSize: Tokens.font.size.normal font: Tokens.font.body.small
elide: Text.ElideRight elide: Text.ElideRight
} }
} }

View file

@ -4,5 +4,5 @@ import Quickshell
Singleton { Singleton {
property int itemWidth: 103 property int itemWidth: 103
property int sidebarWidth: 200 property int sidebarWidth: 230
} }

View file

@ -0,0 +1,75 @@
import QtQuick
import Quickshell
import qs.components
Image {
id: root
property bool hadPrevious
property bool fadingOut
property bool preventInit
property int fadeOutAnim: Anim.FastEffects
property int fadeInAnim: Anim.DefaultEffects
property int fadeInLargeAnim: Anim.StandardLarge
function maybeStartInAnim(): void {
if (!preventInit && !opacityInAnim.running && status === Image.Ready) {
opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim;
opacityInAnim.start();
}
}
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
retainWhileLoading: true
opacity: 0
onStatusChanged: maybeStartInAnim()
onPreventInitChanged: maybeStartInAnim()
Anim on opacity {
id: opacityInAnim
running: false
to: 1
}
Behavior on source {
SequentialAnimation {
ScriptAction {
script: opacityInAnim.stop()
}
PropertyAction {
target: root
property: "fadingOut"
value: true
}
Anim {
target: root
property: "opacity"
to: 0
type: root.fadeOutAnim
}
PropertyAction {
target: root
property: "fadingOut"
value: false
}
PropertyAction {
target: root
property: "hadPrevious"
value: root.source
}
PropertyAction {}
ScriptAction {
script: root.maybeStartInAnim()
}
}
}
}

View file

@ -0,0 +1,107 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import M3Shapes
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.components.images
import qs.services
Item {
id: root
readonly property alias shape: shape
property bool hadPrevious
property color fallbackColour: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2)
// Slight glow to separate from bg
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
blurMax: 1
shadowColor: Colours.palette.m3outline
shadowOpacity: 0.3
}
Behavior on fallbackColour {
CAnim {}
}
Item {
id: shapeWrapper
anchors.fill: parent
layer.enabled: true
opacity: root.fallbackColour.a
MaterialShape {
id: shape
implicitSize: root.width
shape: MaterialShape.Cookie12Sided
color: Qt.alpha(root.fallbackColour, 1)
Anim on rotation {
running: true
paused: !Players.active?.isPlaying
from: 360
to: 0
duration: 23500
easing.type: Easing.Linear
loops: Animation.Infinite
}
}
}
MaterialIcon {
anchors.centerIn: parent
grade: 200
text: image.status === Image.Error ? "broken_image" : "art_track"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build()
opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0
animate: true
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Loader {
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: image.status === Image.Loading ? 1 : 0
sourceComponent: LoadingIndicator {
implicitSize: root.width * 0.3
color: Colours.palette.m3primaryContainer
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
FadeImage {
id: image
anchors.fill: parent
source: Players.getArtUrl(Players.active)
layer.enabled: true
layer.effect: Mask {
maskSource: shapeWrapper
}
}
}

View file

@ -8,13 +8,13 @@ StyledRect {
required property int extra required property int extra
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Tokens.padding.normal anchors.margins: Tokens.padding.medium
color: Colours.palette.m3tertiary color: Colours.palette.m3tertiary
radius: Tokens.rounding.small radius: Tokens.rounding.medium
implicitWidth: count.implicitWidth + Tokens.padding.normal * 2 implicitWidth: count.implicitWidth + Tokens.padding.medium * 2
implicitHeight: count.implicitHeight + Tokens.padding.small * 2 implicitHeight: count.implicitHeight + Tokens.padding.small
opacity: extra > 0 ? 1 : 0 opacity: extra > 0 ? 1 : 0
scale: extra > 0 ? 1 : 0.5 scale: extra > 0 ? 1 : 0.5
@ -38,6 +38,7 @@ StyledRect {
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
duration: Tokens.anim.durations.expressiveFastSpatial duration: Tokens.anim.durations.expressiveFastSpatial
} }
} }

View file

@ -0,0 +1,47 @@
import QtQuick
import QtQuick.Shapes
import qs.components
import qs.services
Shape {
id: root
property color color: Colours.palette.m3surfaceContainer
property int waves: 4
property real amplitude: 3
readonly property real waveHeight: amplitude * 2
preferredRendererType: Shape.CurveRenderer
asynchronous: true
ShapePath {
strokeWidth: 0
strokeColor: "transparent"
fillColor: root.color
PathSvg {
path: {
const w = root.width;
const h = root.height;
const a = root.amplitude;
const n = Math.max(1, root.waves);
const wl = w / n;
const half = wl / 2;
let d = `M 0,${a} `;
for (let i = 0; i < n; ++i) {
const x = i * wl;
d += `Q ${x + half / 2},${-a} ${x + half},${a} `;
d += `Q ${x + half + half / 2},${3 * a} ${x + wl},${a} `;
}
d += `L ${w},${h} L 0,${h} Z`;
return d;
}
}
Behavior on fillColor {
CAnim {}
}
}
}

6
modules/GSFLoader.qml Normal file
View file

@ -0,0 +1,6 @@
import QtQuick
import Quickshell
FontLoader {
source: Quickshell.shellPath("assets/google-sans-flex/GoogleSansFlex-VariableFont_GRAD,ROND,opsz,slnt,wdth,wght.ttf")
}

View file

@ -4,7 +4,7 @@ import Quickshell.Io
import Caelestia import Caelestia
import qs.components.misc import qs.components.misc
import qs.services import qs.services
import qs.modules.controlcenter import qs.modules.nexus
Scope { Scope {
id: root id: root
@ -15,8 +15,8 @@ Scope {
// qmllint disable unresolved-type // qmllint disable unresolved-type
CustomShortcut { CustomShortcut {
// qmllint enable unresolved-type // qmllint enable unresolved-type
name: "controlCenter" name: "nexus"
description: "Open control center" description: "Open nexus"
onPressed: WindowFactory.create() onPressed: WindowFactory.create()
} }
@ -133,7 +133,7 @@ Scope {
WindowFactory.create(); WindowFactory.create();
} }
target: "controlCenter" target: "nexus"
} }
IpcHandler { IpcHandler {

View file

@ -1,12 +1,12 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Caelestia import Caelestia
import qs.components import qs.components
import qs.components.effects
import qs.services import qs.services
MouseArea { MouseArea {
@ -168,17 +168,17 @@ MouseArea {
to: 0 to: 0
type: Anim.StandardLarge type: Anim.StandardLarge
} }
ExAnim { Anim {
target: root target: root
properties: "rsx,rsy" properties: "rsx,rsy"
to: 0 to: 0
} }
ExAnim { Anim {
target: root target: root
property: "sw" property: "sw"
to: root.screen.width to: root.screen.width
} }
ExAnim { Anim {
target: root target: root
property: "sh" property: "sh"
to: root.screen.height to: root.screen.height
@ -230,12 +230,9 @@ MouseArea {
opacity: 0.3 opacity: 0.3
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: Mask {
maskSource: selectionWrapper maskSource: selectionWrapper
maskEnabled: true
maskInverted: true maskInverted: true
maskSpreadAtMin: 1
maskThresholdMin: 0.5
} }
} }
@ -284,28 +281,24 @@ MouseArea {
Behavior on rsx { Behavior on rsx {
enabled: !root.pressed enabled: !root.pressed
ExAnim {} Anim {}
} }
Behavior on rsy { Behavior on rsy {
enabled: !root.pressed enabled: !root.pressed
ExAnim {} Anim {}
} }
Behavior on sw { Behavior on sw {
enabled: !root.pressed enabled: !root.pressed
ExAnim {} Anim {}
} }
Behavior on sh { Behavior on sh {
enabled: !root.pressed enabled: !root.pressed
ExAnim {} Anim {}
}
component ExAnim: Anim {
type: Anim.DefaultSpatial
} }
} }

View file

@ -57,8 +57,8 @@ Variants {
asynchronous: true asynchronous: true
active: Config.background.desktopClock.enabled active: Config.background.desktopClock.enabled
anchors.margins: Tokens.padding.large * 2 anchors.margins: Tokens.padding.extraLargeIncreased
anchors.leftMargin: Tokens.padding.large * 2 + Tokens.sizes.bar.innerWidth + Math.max(Tokens.padding.smaller, Config.border.thickness) anchors.leftMargin: Tokens.padding.extraLargeIncreased + Tokens.sizes.bar.innerWidth + Math.max(Tokens.padding.small, Config.border.thickness)
state: Config.background.desktopClock.position state: Config.background.desktopClock.position
states: [ states: [

View file

@ -24,7 +24,7 @@ Item {
readonly property color safeTertiary: useLightSet ? Colours.palette.m3tertiaryContainer : Colours.palette.m3tertiary readonly property color safeTertiary: useLightSet ? Colours.palette.m3tertiaryContainer : Colours.palette.m3tertiary
implicitWidth: layout.implicitWidth + (Tokens.padding.large * 4 * root.clockScale) implicitWidth: layout.implicitWidth + (Tokens.padding.large * 4 * root.clockScale)
implicitHeight: layout.implicitHeight + (Tokens.padding.large * 2 * root.clockScale) implicitHeight: layout.implicitHeight + (Tokens.padding.extraLargeIncreased * root.clockScale)
Item { Item {
id: clockContainer id: clockContainer
@ -63,7 +63,7 @@ Item {
visible: root.bgEnabled visible: root.bgEnabled
anchors.fill: parent anchors.fill: parent
radius: Tokens.rounding.large * root.clockScale radius: Tokens.rounding.extraLarge * root.clockScale
opacity: Config.background.desktopClock.background.opacity opacity: Config.background.desktopClock.background.opacity
color: Colours.palette.m3surface color: Colours.palette.m3surface
@ -74,21 +74,20 @@ Item {
id: layout id: layout
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.larger * root.clockScale spacing: Tokens.spacing.large * root.clockScale
RowLayout { RowLayout {
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
StyledText { StyledText {
text: Time.hourStr text: Time.hourStr
font.pointSize: Tokens.font.size.extraLarge * 3 * root.clockScale font: Tokens.font.clock.size(Tokens.font.headline.medium.pointSize * 3 * root.clockScale).weight(Font.Bold).build()
font.weight: Font.Bold
color: root.safePrimary color: root.safePrimary
} }
StyledText { StyledText {
text: ":" text: ":"
font.pointSize: Tokens.font.size.extraLarge * 3 * root.clockScale font: Tokens.font.clock.size(Tokens.font.headline.medium.pointSize * 3 * root.clockScale).build()
color: root.safeTertiary color: root.safeTertiary
opacity: 0.8 opacity: 0.8
Layout.topMargin: -Tokens.padding.large * 1.5 * root.clockScale Layout.topMargin: -Tokens.padding.large * 1.5 * root.clockScale
@ -96,8 +95,7 @@ Item {
StyledText { StyledText {
text: Time.minuteStr text: Time.minuteStr
font.pointSize: Tokens.font.size.extraLarge * 3 * root.clockScale font: Tokens.font.clock.size(Tokens.font.headline.medium.pointSize * 3 * root.clockScale).weight(Font.Bold).build()
font.weight: Font.Bold
color: root.safeSecondary color: root.safeSecondary
} }
@ -111,7 +109,7 @@ Item {
sourceComponent: StyledText { sourceComponent: StyledText {
text: Time.amPmStr text: Time.amPmStr
font.pointSize: Tokens.font.size.large * root.clockScale font: Tokens.font.clock.size(Tokens.font.title.medium.pointSize * root.clockScale).build()
color: root.safeSecondary color: root.safeSecondary
} }
} }
@ -120,8 +118,8 @@ Item {
StyledRect { StyledRect {
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: 4 * root.clockScale Layout.preferredWidth: 4 * root.clockScale
Layout.topMargin: Tokens.spacing.larger * root.clockScale Layout.topMargin: Tokens.spacing.large * root.clockScale
Layout.bottomMargin: Tokens.spacing.larger * root.clockScale Layout.bottomMargin: Tokens.spacing.large * root.clockScale
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: root.safePrimary color: root.safePrimary
opacity: 0.8 opacity: 0.8
@ -132,24 +130,19 @@ Item {
StyledText { StyledText {
text: Time.format("MMMM").toUpperCase() text: Time.format("MMMM").toUpperCase()
font.pointSize: Tokens.font.size.large * root.clockScale font: Tokens.font.clock.size(Tokens.font.title.medium.pointSize * root.clockScale).letterSpacing(4).weight(Font.Bold).build()
font.letterSpacing: 4
font.weight: Font.Bold
color: root.safeSecondary color: root.safeSecondary
} }
StyledText { StyledText {
text: Time.format("dd") text: Time.format("dd")
font.pointSize: Tokens.font.size.extraLarge * root.clockScale font: Tokens.font.clock.size(Tokens.font.headline.medium.pointSize * root.clockScale).letterSpacing(2).weight(Font.Medium).build()
font.letterSpacing: 2
font.weight: Font.Medium
color: root.safePrimary color: root.safePrimary
} }
StyledText { StyledText {
text: Time.format("dddd") text: Time.format("dddd")
font.pointSize: Tokens.font.size.larger * root.clockScale font: Tokens.font.clock.size(Tokens.font.body.large.pointSize * root.clockScale).letterSpacing(2).build()
font.letterSpacing: 2
color: root.safeSecondary color: root.safeSecondary
} }
} }
@ -157,9 +150,7 @@ Item {
} }
Behavior on clockScale { Behavior on clockScale {
Anim { Anim {}
type: Anim.DefaultSpatial
}
} }
Behavior on implicitWidth { Behavior on implicitWidth {

View file

@ -65,8 +65,8 @@ Item {
values: Audio.cava.values values: Audio.cava.values
primaryColor: Qt.alpha(Colours.palette.m3primary, 0.7) primaryColor: Qt.alpha(Colours.palette.m3primary, 0.7)
secondaryColor: Qt.alpha(Colours.palette.m3inversePrimary, 0.7) secondaryColor: Qt.alpha(Colours.palette.m3inversePrimary, 0.7)
rounding: Tokens.rounding.small * Config.background.visualiser.rounding rounding: Tokens.rounding.medium * Config.background.visualiser.rounding
spacing: Tokens.spacing.small * Config.background.visualiser.spacing spacing: Tokens.spacing.extraSmall * Config.background.visualiser.spacing
animationDuration: Tokens.anim.durations.normal animationDuration: Tokens.anim.durations.normal
Behavior on anchors.leftMargin { Behavior on anchors.leftMargin {
@ -87,6 +87,8 @@ Item {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }

View file

@ -12,22 +12,24 @@ Item {
id: root id: root
property string source: Wallpapers.current property string source: Wallpapers.current
property Image current: one property CachingImage current
property bool completed property bool completed
onSourceChanged: { onSourceChanged: {
if (!source) if (!source)
current = null; current = null;
else if (current === one)
two.update();
else else
one.update(); current = imgComp.createObject(this, {
path: source
});
} }
Component.onCompleted: { Component.onCompleted: {
if (source) if (source)
Qt.callLater(() => { Qt.callLater(() => {
one.update(); current = imgComp.createObject(this, {
path: source
});
completed = true; completed = true;
}); });
} }
@ -43,12 +45,12 @@ Item {
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.large spacing: Tokens.spacing.largeIncreased
MaterialIcon { MaterialIcon {
text: "sentiment_stressed" text: "sentiment_stressed"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.extraLarge * 5 fontStyle: Tokens.font.icon.builders.extraLarge.scale(5).build()
} }
Column { Column {
@ -58,13 +60,12 @@ Item {
StyledText { StyledText {
text: qsTr("Wallpaper missing?") text: qsTr("Wallpaper missing?")
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.extraLarge * 2 font: Tokens.font.body.builders.large.size(28 * 2).weight(Font.Bold).build()
font.bold: true
} }
StyledRect { StyledRect {
implicitWidth: selectWallText.implicitWidth + Tokens.padding.large * 2 implicitWidth: selectWallText.implicitWidth + Tokens.padding.extraLargeIncreased
implicitHeight: selectWallText.implicitHeight + Tokens.padding.small * 2 implicitHeight: selectWallText.implicitHeight + Tokens.padding.small
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Colours.palette.m3primary color: Colours.palette.m3primary
@ -91,7 +92,7 @@ Item {
text: qsTr("Set it now!") text: qsTr("Set it now!")
color: Colours.palette.m3onPrimary color: Colours.palette.m3onPrimary
font.pointSize: Tokens.font.size.large font: Tokens.font.body.large
} }
} }
} }
@ -99,48 +100,34 @@ Item {
} }
} }
Img { Component {
id: one id: imgComp
}
Img { CachingImage {
id: two
}
component Img: CachingImage {
id: img id: img
function update(): void {
if (path === root.source)
root.current = this;
else
path = root.source;
}
anchors.fill: parent anchors.fill: parent
opacity: 0 opacity: 0
scale: Wallpapers.showPreview ? 1 : 0.8
onStatusChanged: { onStatusChanged: {
if (status === Image.Ready) if (status === Image.Ready)
root.current = this; anim.start();
} }
states: State { Anim on opacity {
name: "visible" id: anim
when: root.current === img
PropertyChanges { type: Anim.SlowEffects
img.opacity: 1 running: false
img.scale: 1 from: 0
} to: 1
} }
transitions: Transition { Timer {
Anim { running: root.current !== img && root.current?.status === Image.Ready
target: img interval: anim.duration
properties: "opacity,scale" onTriggered: img.destroy()
} }
} }
} }

View file

@ -102,7 +102,7 @@ ColumnLayout {
} }
} }
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
Repeater { Repeater {
id: repeater id: repeater

View file

@ -18,7 +18,7 @@ Item {
readonly property bool disabled: Strings.testRegexList(Config.bar.excludedScreens, screen.name) readonly property bool disabled: Strings.testRegexList(Config.bar.excludedScreens, screen.name)
readonly property int clampedWidth: Math.max(Config.border.minThickness, implicitWidth) readonly property int clampedWidth: Math.max(Config.border.minThickness, implicitWidth)
readonly property int padding: Math.max(Tokens.padding.smaller, Config.border.thickness) readonly property int padding: Math.max(Tokens.padding.small, Config.border.thickness)
readonly property int contentWidth: Tokens.sizes.bar.innerWidth + padding * 2 readonly property int contentWidth: Tokens.sizes.bar.innerWidth + padding * 2
readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentWidth : Config.border.thickness readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentWidth : Config.border.thickness
readonly property bool shouldBeVisible: !fullscreen && !disabled && (Config.bar.persistent || visibilities.bar || isHovered) readonly property bool shouldBeVisible: !fullscreen && !disabled && (Config.bar.persistent || visibilities.bar || isHovered)
@ -37,7 +37,7 @@ Item {
} }
clip: true clip: true
visible: width > 0 visible: width > Config.border.thickness
implicitWidth: fullscreen ? 0 : Config.border.thickness implicitWidth: fullscreen ? 0 : Config.border.thickness
states: State { states: State {
@ -57,7 +57,6 @@ Item {
Anim { Anim {
target: root target: root
property: "implicitWidth" property: "implicitWidth"
type: Anim.DefaultSpatial
} }
}, },
Transition { Transition {
@ -79,7 +78,7 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
active: root.shouldBeVisible || root.visible active: root.shouldBeVisible
sourceComponent: Bar { sourceComponent: Bar {
width: root.contentWidth width: root.contentWidth

View file

@ -86,8 +86,7 @@ Item {
id: metrics id: metrics
text: root.windowTitle text: root.windowTitle
font.pointSize: root.Tokens.font.size.smaller font: root.Tokens.font.body.builders.small.letterSpacing(1.4).build()
font.family: root.Tokens.font.family.mono
elide: Qt.ElideRight elide: Qt.ElideRight
elideWidth: root.maxHeight - icon.height elideWidth: root.maxHeight - icon.height
@ -100,9 +99,7 @@ Item {
} }
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {}
type: Anim.DefaultSpatial
}
} }
component Title: StyledText { component Title: StyledText {
@ -112,8 +109,7 @@ Item {
anchors.top: icon.bottom anchors.top: icon.bottom
anchors.topMargin: Tokens.spacing.small anchors.topMargin: Tokens.spacing.small
font.pointSize: metrics.font.pointSize font: metrics.font
font.family: metrics.font.family
color: root.colour color: root.colour
opacity: root.current === this ? 1 : 0 opacity: root.current === this ? 1 : 0
@ -132,7 +128,9 @@ Item {
height: implicitWidth height: implicitWidth
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
} }

View file

@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts
import Caelestia.Config import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
@ -9,7 +10,8 @@ StyledRect {
id: root id: root
readonly property color colour: Colours.palette.m3tertiary readonly property color colour: Colours.palette.m3tertiary
readonly property int padding: Config.bar.clock.background ? Tokens.padding.normal : Tokens.padding.small readonly property int padding: Config.bar.clock.background ? Tokens.padding.medium : Tokens.padding.extraSmall
readonly property var font: Tokens.font.body.builders.small.scale(1.1)
implicitWidth: Tokens.sizes.bar.innerWidth implicitWidth: Tokens.sizes.bar.innerWidth
implicitHeight: layout.implicitHeight + root.padding * 2 implicitHeight: layout.implicitHeight + root.padding * 2
@ -17,16 +19,15 @@ StyledRect {
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Config.bar.clock.background ? Colours.tPalette.m3surfaceContainer.a : 0) color: Qt.alpha(Colours.tPalette.m3surfaceContainer, Config.bar.clock.background ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.full radius: Tokens.rounding.full
Column { ColumnLayout {
id: layout id: layout
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
Loader { Loader {
Layout.alignment: Qt.AlignHCenter
asynchronous: true asynchronous: true
anchors.horizontalCenter: parent.horizontalCenter
active: Config.bar.clock.showIcon active: Config.bar.clock.showIcon
visible: active visible: active
@ -37,35 +38,69 @@ StyledRect {
} }
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter Layout.alignment: Qt.AlignHCenter
visible: Config.bar.clock.showDate visible: Config.bar.clock.showDate
horizontalAlignment: StyledText.AlignHCenter horizontalAlignment: StyledText.AlignHCenter
text: Time.format("ddd\nd") text: Time.format("ddd\nd")
font.pointSize: Tokens.font.size.smaller font: Tokens.font.body.small
font.family: Tokens.font.family.sans
color: root.colour color: root.colour
} }
Rectangle { Rectangle {
anchors.horizontalCenter: parent.horizontalCenter Layout.fillWidth: true
visible: Config.bar.clock.showDate visible: Config.bar.clock.showDate
height: visible ? 1 : 0 implicitHeight: 1
color: Colours.palette.m3outlineVariant
width: parent.width * 0.8
color: root.colour
opacity: 0.2
} }
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter Layout.alignment: Qt.AlignHCenter
text: Time.hourStr
font: {
const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / hourMetrics.width);
return root.font.width(scale * 100).letterSpacing(scale).build();
}
color: root.colour
horizontalAlignment: StyledText.AlignHCenter TextMetrics {
text: Time.format(GlobalConfig.services.useTwelveHourClock ? "hh\nmm\nA" : "hh\nmm") id: hourMetrics
font.pointSize: Tokens.font.size.smaller
font.family: Tokens.font.family.mono font: root.font.build()
text: Time.hourStr
}
}
StyledText {
Layout.topMargin: -parent.spacing - 4
Layout.alignment: Qt.AlignHCenter
text: Time.minuteStr
font: {
const scale = text === "11" ? 1.15 : Math.min(1.05, Math.max(hourMetrics.width, minMetrics.width) / minMetrics.width);
return root.font.width(scale * 100).letterSpacing(scale).build();
}
color: root.colour
TextMetrics {
id: minMetrics
font: root.font.build()
text: Time.minuteStr
}
}
Loader {
Layout.topMargin: -parent.spacing - 4
Layout.alignment: Qt.AlignHCenter
asynchronous: true
active: GlobalConfig.services.useTwelveHourClock
visible: active
sourceComponent: StyledText {
text: Time.amPmStr.toLowerCase()
font: Tokens.font.body.builders.small.scale(0.9).build()
color: root.colour color: root.colour
} }
} }
}
} }

View file

@ -8,8 +8,8 @@ import qs.utils
Item { Item {
id: root id: root
implicitWidth: Math.round(Tokens.font.size.large * 1.2) implicitWidth: Math.round(Tokens.font.body.large.pointSize * 1.2)
implicitHeight: Math.round(Tokens.font.size.large * 1.2) implicitHeight: Math.round(Tokens.font.body.large.pointSize * 1.2)
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -30,8 +30,8 @@ Item {
id: caelestiaLogo id: caelestiaLogo
Logo { Logo {
implicitWidth: Math.round(Tokens.font.size.large * 1.6) implicitWidth: Math.round(Tokens.font.body.large.pointSize * 1.6)
implicitHeight: Math.round(Tokens.font.size.large * 1.6) implicitHeight: Math.round(Tokens.font.body.large.pointSize * 1.6)
} }
} }
@ -40,7 +40,7 @@ Item {
ColouredIcon { ColouredIcon {
source: SysInfo.osLogo source: SysInfo.osLogo
implicitSize: Math.round(Tokens.font.size.large * 1.2) implicitSize: Math.round(Tokens.font.body.large.pointSize * 1.2)
colour: Colours.palette.m3tertiary colour: Colours.palette.m3tertiary
} }
} }

View file

@ -8,7 +8,7 @@ Item {
required property DrawerVisibilities visibilities required property DrawerVisibilities visibilities
implicitWidth: icon.implicitHeight + Tokens.padding.small * 2 implicitWidth: icon.implicitHeight + Tokens.padding.small
implicitHeight: icon.implicitHeight implicitHeight: icon.implicitHeight
StateLayer { StateLayer {
@ -16,7 +16,7 @@ Item {
anchors.fill: undefined anchors.fill: undefined
anchors.centerIn: parent anchors.centerIn: parent
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.small * 2 implicitHeight: icon.implicitHeight + Tokens.padding.small
radius: Tokens.rounding.full radius: Tokens.rounding.full
onClicked: root.visibilities.session = !root.visibilities.session onClicked: root.visibilities.session = !root.visibilities.session
} }
@ -29,7 +29,6 @@ Item {
text: "power_settings_new" text: "power_settings_new"
color: Colours.palette.m3error color: Colours.palette.m3error
font.bold: true fontStyle: Tokens.font.icon.builders.small.weight(Font.Bold).build()
font.pointSize: Tokens.font.size.normal
} }
} }

View file

@ -21,7 +21,7 @@ StyledRect {
clip: true clip: true
implicitWidth: Tokens.sizes.bar.innerWidth implicitWidth: Tokens.sizes.bar.innerWidth
implicitHeight: iconColumn.implicitHeight + Tokens.padding.normal * 2 - (Config.bar.status.showLockStatus && !Hypr.capsLock && !Hypr.numLock ? iconColumn.spacing : 0) implicitHeight: iconColumn.implicitHeight + Tokens.padding.medium * 2 - (Config.bar.status.showLockStatus && !Hypr.capsLock && !Hypr.numLock ? iconColumn.spacing : 0)
ColumnLayout { ColumnLayout {
id: iconColumn id: iconColumn
@ -29,9 +29,9 @@ StyledRect {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: Tokens.padding.normal anchors.bottomMargin: Tokens.padding.medium
spacing: Tokens.spacing.smaller / 2 spacing: Tokens.spacing.medium / 2
// Lock keys status // Lock keys status
WrappedLoader { WrappedLoader {
@ -57,7 +57,9 @@ StyledRect {
color: root.colour color: root.colour
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -88,7 +90,9 @@ StyledRect {
color: root.colour color: root.colour
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -136,7 +140,7 @@ StyledRect {
animate: true animate: true
text: Hypr.kbLayout text: Hypr.kbLayout
color: root.colour color: root.colour
font.family: Tokens.font.family.mono font: Tokens.font.mono.medium
} }
} }
@ -172,7 +176,7 @@ StyledRect {
active: Config.bar.status.showBluetooth active: Config.bar.status.showBluetooth
sourceComponent: ColumnLayout { sourceComponent: ColumnLayout {
spacing: Tokens.spacing.smaller / 2 spacing: Tokens.spacing.medium / 2
// Bluetooth icon // Bluetooth icon
MaterialIcon { MaterialIcon {
@ -245,15 +249,7 @@ StyledRect {
return "rocket_launch"; return "rocket_launch";
return "balance"; return "balance";
} }
return Icons.getBatteryIcon(UPower.displayDevice.percentage, [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state));
const perc = UPower.displayDevice.percentage;
const charging = [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state);
if (perc === 1)
return charging ? "battery_charging_full" : "battery_full";
let level = Math.floor(perc * 7);
if (charging && (level === 4 || level === 1))
level--;
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
} }
color: !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? root.colour : Colours.palette.m3error color: !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? root.colour : Colours.palette.m3error
fill: 1 fill: 1

View file

@ -14,7 +14,7 @@ StyledRect {
readonly property alias items: items readonly property alias items: items
readonly property alias expandIcon: expandIcon readonly property alias expandIcon: expandIcon
readonly property int padding: Config.bar.tray.background ? Tokens.padding.normal : Tokens.padding.small readonly property int padding: Config.bar.tray.background ? Tokens.padding.medium : Tokens.padding.extraSmall
readonly property int spacing: Config.bar.tray.background ? Tokens.spacing.small : 0 readonly property int spacing: Config.bar.tray.background ? Tokens.spacing.small : 0
property bool expanded property bool expanded
@ -75,7 +75,9 @@ StyledRect {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -91,16 +93,16 @@ StyledRect {
sourceComponent: Item { sourceComponent: Item {
implicitWidth: expandIconInner.implicitWidth implicitWidth: expandIconInner.implicitWidth
implicitHeight: expandIconInner.implicitHeight - Tokens.padding.small * 2 implicitHeight: expandIconInner.implicitHeight - Tokens.padding.small
MaterialIcon { MaterialIcon {
id: expandIconInner id: expandIconInner
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: Config.bar.tray.background ? Tokens.padding.small : -Tokens.padding.small anchors.bottomMargin: Config.bar.tray.background ? Tokens.padding.extraSmall : -Tokens.padding.extraSmall
text: "expand_less" text: "expand_less"
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.large
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0
Behavior on rotation { Behavior on rotation {
@ -115,8 +117,6 @@ StyledRect {
} }
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {}
type: Anim.DefaultSpatial
}
} }
} }

View file

@ -13,8 +13,8 @@ MouseArea {
required property SystemTrayItem modelData required property SystemTrayItem modelData
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
implicitWidth: Tokens.font.size.small * 2 implicitWidth: Tokens.font.body.small.pointSize * 2
implicitHeight: Tokens.font.size.small * 2 implicitHeight: Tokens.font.body.small.pointSize * 2
onClicked: event => { onClicked: event => {
if (event.button === Qt.LeftButton) if (event.button === Qt.LeftButton)

View file

@ -44,7 +44,7 @@ StyledRect {
clip: true clip: true
y: offset + mask.y y: offset + mask.y
implicitWidth: Tokens.sizes.bar.innerWidth - Tokens.padding.small * 2 implicitWidth: Tokens.sizes.bar.innerWidth - Tokens.padding.small
implicitHeight: size implicitHeight: size
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Colours.palette.m3primary color: Colours.palette.m3primary

View file

@ -65,7 +65,7 @@ Item {
anchors.horizontalCenter: root.horizontalCenter anchors.horizontalCenter: root.horizontalCenter
y: (start?.y ?? 0) - 1 y: (start?.y ?? 0) - 1
implicitWidth: Tokens.sizes.bar.innerWidth - Tokens.padding.small * 2 + 2 implicitWidth: Tokens.sizes.bar.innerWidth - Tokens.padding.small + 2
implicitHeight: start && end ? end.y + end.size - start.y + 2 : 0 implicitHeight: start && end ? end.y + end.size - start.y + 2 : 0
color: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2) color: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)

View file

@ -18,7 +18,7 @@ Item {
readonly property string activeSpecial: (GlobalConfig.bar.workspaces.perMonitorWorkspaces ? monitor : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace?.name ?? "" readonly property string activeSpecial: (GlobalConfig.bar.workspaces.perMonitorWorkspaces ? monitor : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace?.name ?? ""
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: Mask {
maskSource: mask maskSource: mask
} }
@ -65,7 +65,9 @@ Item {
opacity: view.contentY > 0 ? 0 : 1 opacity: view.contentY > 0 ? 0 : 1
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -76,10 +78,12 @@ Item {
radius: Tokens.rounding.full radius: Tokens.rounding.full
implicitHeight: parent.height / 2 implicitHeight: parent.height / 2
opacity: view.contentY < view.contentHeight - parent.height + Tokens.padding.small ? 0 : 1 opacity: view.contentY < view.contentHeight - parent.height + Tokens.padding.extraSmall ? 0 : 1
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
} }
@ -88,7 +92,7 @@ Item {
id: view id: view
anchors.fill: parent anchors.fill: parent
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
interactive: false interactive: false
currentIndex: model.values.findIndex(w => w.name === root.activeSpecial) currentIndex: model.values.findIndex(w => w.name === root.activeSpecial)
@ -213,7 +217,7 @@ Item {
drag.target: view.contentItem drag.target: view.contentItem
drag.axis: Drag.YAxis drag.axis: Drag.YAxis
drag.maximumY: 0 drag.maximumY: 0
drag.minimumY: Math.min(0, view.height - view.contentHeight - Tokens.padding.small) drag.minimumY: Math.min(0, view.height - view.contentHeight - Tokens.padding.extraSmall)
onPressed: event => startY = event.y onPressed: event => startY = event.y
@ -233,7 +237,7 @@ Item {
id: ws id: ws
required property HyprlandWorkspace modelData required property HyprlandWorkspace modelData
readonly property int size: label.Layout.preferredHeight + (hasWindows ? windows.implicitHeight + Tokens.padding.small : 0) readonly property int size: label.Layout.preferredHeight + (hasWindows ? windows.implicitHeight + Tokens.padding.extraSmall : 0)
property int wsId property int wsId
property string icon property string icon
property bool hasWindows property bool hasWindows
@ -284,7 +288,7 @@ Item {
asynchronous: true asynchronous: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.preferredHeight: Tokens.sizes.bar.innerWidth - Tokens.padding.small * 2 Layout.preferredHeight: Tokens.sizes.bar.innerWidth - Tokens.padding.small
sourceComponent: ws.icon.length === 1 ? letterComp : iconComp sourceComponent: ws.icon.length === 1 ? letterComp : iconComp

View file

@ -18,7 +18,7 @@ ColumnLayout {
readonly property bool isWorkspace: true // Flag for finding workspace children readonly property bool isWorkspace: true // Flag for finding workspace children
// Unanimated prop for others to use as reference // Unanimated prop for others to use as reference
readonly property int size: implicitHeight + (hasWindows ? Tokens.padding.small : 0) readonly property int size: implicitHeight + (hasWindows ? Tokens.padding.extraSmall : 0)
readonly property int ws: groupOffset + index + 1 readonly property int ws: groupOffset + index + 1
readonly property bool isOccupied: occupied[ws] ?? false readonly property bool isOccupied: occupied[ws] ?? false
@ -33,7 +33,7 @@ ColumnLayout {
id: indicator id: indicator
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.preferredHeight: Tokens.sizes.bar.innerWidth - Tokens.padding.small * 2 Layout.preferredHeight: Tokens.sizes.bar.innerWidth - Tokens.padding.small
animate: true animate: true
text: { text: {
@ -52,6 +52,7 @@ ColumnLayout {
} }
color: Config.bar.workspaces.occupiedBg || root.isOccupied || root.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.layer(Colours.palette.m3outlineVariant, 2) color: Config.bar.workspaces.occupiedBg || root.isOccupied || root.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.layer(Colours.palette.m3outlineVariant, 2)
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
font.family: "Rubik" // Hard code rubik for now since google sans doesn't play well with certain unicode symbols apparently
} }
Loader { Loader {

View file

@ -28,7 +28,7 @@ StyledClippingRect {
property real blur: onSpecial ? 1 : 0 property real blur: onSpecial ? 1 : 0
implicitWidth: Tokens.sizes.bar.innerWidth implicitWidth: Tokens.sizes.bar.innerWidth
implicitHeight: layout.implicitHeight + Tokens.padding.small * 2 implicitHeight: layout.implicitHeight + Tokens.padding.small
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.full radius: Tokens.rounding.full
@ -51,7 +51,7 @@ StyledClippingRect {
active: Config.bar.workspaces.occupiedBg active: Config.bar.workspaces.occupiedBg
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.extraSmall
sourceComponent: OccupiedBg { sourceComponent: OccupiedBg {
workspaces: workspaces workspaces: workspaces
@ -64,7 +64,7 @@ StyledClippingRect {
id: layout id: layout
anchors.centerIn: parent anchors.centerIn: parent
spacing: Math.floor(Tokens.spacing.small / 2) spacing: Math.floor(Tokens.spacing.extraSmall)
Repeater { Repeater {
id: workspaces id: workspaces
@ -108,7 +108,9 @@ StyledClippingRect {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -118,7 +120,7 @@ StyledClippingRect {
asynchronous: true asynchronous: true
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.extraSmall
active: opacity > 0 active: opacity > 0
@ -134,7 +136,9 @@ StyledClippingRect {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }

View file

@ -12,21 +12,21 @@ Item {
required property PopoutState popouts required property PopoutState popouts
implicitWidth: Hypr.activeToplevel ? child.implicitWidth : -Tokens.padding.large * 2 implicitWidth: Hypr.activeToplevel ? child.implicitWidth : -Tokens.padding.extraLargeIncreased
implicitHeight: child.implicitHeight implicitHeight: child.implicitHeight
Column { Column {
id: child id: child
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
RowLayout { RowLayout {
id: detailsRow id: detailsRow
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
IconImage { IconImage {
id: icon id: icon
@ -46,7 +46,7 @@ Item {
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true
text: Hypr.activeToplevel?.title ?? "" text: Hypr.activeToplevel?.title ?? ""
font.pointSize: Tokens.font.size.normal font: Tokens.font.body.medium
elide: Text.ElideRight elide: Text.ElideRight
} }
@ -59,13 +59,13 @@ Item {
} }
Item { Item {
implicitWidth: expandIcon.implicitHeight + Tokens.padding.small * 2 implicitWidth: expandIcon.implicitHeight + Tokens.padding.small
implicitHeight: expandIcon.implicitHeight + Tokens.padding.small * 2 implicitHeight: expandIcon.implicitHeight + Tokens.padding.small
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
StateLayer { StateLayer {
radius: Tokens.rounding.normal radius: Tokens.rounding.large
onClicked: root.popouts.detachRequested("winfo") onClicked: root.popouts.detachRequested("winfo")
} }
@ -77,14 +77,14 @@ Item {
text: "chevron_right" text: "chevron_right"
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.large
} }
} }
} }
ClippingWrapperRectangle { ClippingWrapperRectangle {
color: "transparent" color: "transparent"
radius: Tokens.rounding.small radius: Tokens.rounding.medium
ScreencopyView { ScreencopyView {
id: preview id: preview

View file

@ -14,8 +14,8 @@ Item {
required property PopoutState popouts required property PopoutState popouts
implicitWidth: layout.implicitWidth + Tokens.padding.normal * 2 implicitWidth: layout.implicitWidth + Tokens.padding.medium * 2
implicitHeight: layout.implicitHeight + Tokens.padding.normal * 2 implicitHeight: layout.implicitHeight + Tokens.padding.medium * 2
ButtonGroup { ButtonGroup {
id: sinks id: sinks
@ -30,11 +30,11 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
text: qsTr("Output device") text: qsTr("Output device")
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
} }
Repeater { Repeater {
@ -53,9 +53,9 @@ Item {
} }
StyledText { StyledText {
Layout.topMargin: Tokens.spacing.smaller Layout.topMargin: Tokens.spacing.medium
text: qsTr("Input device") text: qsTr("Input device")
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
} }
Repeater { Repeater {
@ -72,15 +72,14 @@ Item {
} }
StyledText { StyledText {
Layout.topMargin: Tokens.spacing.smaller Layout.topMargin: Tokens.spacing.medium
Layout.bottomMargin: -Tokens.spacing.small / 2
text: qsTr("Volume (%1)").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`) text: qsTr("Volume (%1)").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`)
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
} }
CustomMouseArea { CustomMouseArea {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: Tokens.padding.normal * 3 implicitHeight: Tokens.padding.medium * 3
onWheel: event => { onWheel: event => {
if (event.angleDelta.y > 0) if (event.angleDelta.y > 0)
@ -95,20 +94,16 @@ Item {
implicitHeight: parent.implicitHeight implicitHeight: parent.implicitHeight
value: Audio.volume value: Audio.volume
onMoved: Audio.setVolume(value) onInteraction: value => Audio.setVolume(value)
Behavior on value {
Anim {}
}
} }
} }
IconTextButton { IconTextButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.normal Layout.topMargin: Tokens.spacing.medium
inactiveColour: Colours.palette.m3primaryContainer inactiveColour: Colours.palette.m3primaryContainer
inactiveOnColour: Colours.palette.m3onPrimaryContainer inactiveOnColour: Colours.palette.m3onPrimaryContainer
verticalPadding: Tokens.padding.small verticalPadding: Tokens.padding.extraSmall
text: qsTr("Open settings") text: qsTr("Open settings")
icon: "settings" icon: "settings"

View file

@ -9,7 +9,7 @@ import qs.services
Column { Column {
id: root id: root
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
width: Tokens.sizes.bar.batteryWidth width: Tokens.sizes.bar.batteryWidth
StyledText { StyledText {
@ -45,11 +45,11 @@ Column {
height: active ? ((item as Item)?.implicitHeight ?? 0) : 0 height: active ? ((item as Item)?.implicitHeight ?? 0) : 0
sourceComponent: StyledRect { sourceComponent: StyledRect {
implicitWidth: child.implicitWidth + Tokens.padding.normal * 2 implicitWidth: child.implicitWidth + Tokens.padding.medium * 2
implicitHeight: child.implicitHeight + Tokens.padding.smaller * 2 implicitHeight: child.implicitHeight + Tokens.padding.large
color: Colours.palette.m3error color: Colours.palette.m3error
radius: Tokens.rounding.normal radius: Tokens.rounding.large
Column { Column {
id: child id: child
@ -72,8 +72,7 @@ Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: qsTr("Performance Degraded") text: qsTr("Performance Degraded")
color: Colours.palette.m3onError color: Colours.palette.m3onError
font.family: Tokens.font.family.mono font: Tokens.font.mono.builders.medium.weight(Font.Medium).build()
font.weight: 500
} }
MaterialIcon { MaterialIcon {
@ -109,8 +108,8 @@ Column {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + Tokens.padding.normal * 2 + Tokens.spacing.large * 2 implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + Tokens.padding.medium * 2 + Tokens.spacing.largeIncreased * 2
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + Tokens.padding.small * 2 implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + Tokens.padding.small
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.full radius: Tokens.rounding.full
@ -147,9 +146,7 @@ Column {
] ]
transitions: Transition { transitions: Transition {
AnchorAnim { AnchorAnim {}
type: AnchorAnim.Emphasized
}
} }
} }
@ -158,7 +155,7 @@ Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Tokens.padding.small anchors.leftMargin: Tokens.padding.extraSmall
profile: PowerProfile.PowerSaver profile: PowerProfile.PowerSaver
icon: "energy_savings_leaf" icon: "energy_savings_leaf"
@ -178,7 +175,7 @@ Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Tokens.padding.small anchors.rightMargin: Tokens.padding.extraSmall
profile: PowerProfile.Performance profile: PowerProfile.Performance
icon: "rocket_launch" icon: "rocket_launch"
@ -199,8 +196,8 @@ Column {
required property string icon required property string icon
required property int profile required property int profile
implicitWidth: icon.implicitHeight + Tokens.padding.small * 2 implicitWidth: icon.implicitHeight + Tokens.padding.small
implicitHeight: icon.implicitHeight + Tokens.padding.small * 2 implicitHeight: icon.implicitHeight + Tokens.padding.small
StateLayer { StateLayer {
radius: Tokens.rounding.full radius: Tokens.rounding.full
@ -214,12 +211,14 @@ Column {
anchors.centerIn: parent anchors.centerIn: parent
text: parent.icon text: parent.icon
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.large
color: profiles.current === text ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface color: profiles.current === text ? Colours.palette.m3onPrimary : Colours.palette.m3onSurfaceVariant
fill: profiles.current === text ? 1 : 0 fill: profiles.current === text ? 1 : 0
Behavior on fill { Behavior on fill {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
} }

View file

@ -19,10 +19,10 @@ ColumnLayout {
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
StyledText { StyledText {
Layout.topMargin: Tokens.padding.normal Layout.topMargin: Tokens.padding.medium
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
text: qsTr("Bluetooth") text: qsTr("Bluetooth")
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
} }
Toggle { Toggle {
@ -47,7 +47,7 @@ ColumnLayout {
StyledText { StyledText {
Layout.topMargin: Tokens.spacing.small Layout.topMargin: Tokens.spacing.small
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
text: { text: {
const devices = Bluetooth.devices.values; // qmllint disable unresolved-type const devices = Bluetooth.devices.values; // qmllint disable unresolved-type
let available = qsTr("%1 device%2 available").arg(devices.length).arg(devices.length === 1 ? "" : "s"); let available = qsTr("%1 device%2 available").arg(devices.length).arg(devices.length === 1 ? "" : "s");
@ -57,7 +57,7 @@ ColumnLayout {
return available; return available;
} }
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
Repeater { Repeater {
@ -72,7 +72,7 @@ ColumnLayout {
readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting // qmllint disable unresolved-type readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting // qmllint disable unresolved-type
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
opacity: 0 opacity: 0
@ -84,7 +84,9 @@ ColumnLayout {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -96,8 +98,8 @@ ColumnLayout {
} }
StyledText { StyledText {
Layout.leftMargin: Tokens.spacing.small / 2 Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.small / 2 Layout.rightMargin: Tokens.spacing.extraSmall
Layout.fillWidth: true Layout.fillWidth: true
text: device.modelData.name text: device.modelData.name
elide: Text.ElideRight elide: Text.ElideRight
@ -105,15 +107,15 @@ ColumnLayout {
MaterialIcon { MaterialIcon {
visible: device.modelData.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type visible: device.modelData.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type
text: Icons.getBatteryIcon(device.modelData.batteryAvailable ? device.modelData.battery * 100 : -1) text: device.modelData.batteryAvailable ? Icons.getBatteryIcon(device.modelData.battery) : "battery_alert"
color: device.modelData.battery < 0.2 ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant color: device.modelData.batteryAvailable && device.modelData.battery < 0.2 ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant
} }
StyledRect { StyledRect {
id: connectBtn id: connectBtn
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: connectIcon.implicitHeight + Tokens.padding.small implicitHeight: connectIcon.implicitHeight + Tokens.padding.extraSmall
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3primary, device.modelData.state === BluetoothDeviceState.Connected ? 1 : 0) // qmllint disable unresolved-type color: Qt.alpha(Colours.palette.m3primary, device.modelData.state === BluetoothDeviceState.Connected ? 1 : 0) // qmllint disable unresolved-type
@ -140,7 +142,9 @@ ColumnLayout {
opacity: device.loading ? 0 : 1 opacity: device.loading ? 0 : 1
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
} }
@ -169,10 +173,10 @@ ColumnLayout {
IconTextButton { IconTextButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.normal Layout.topMargin: Tokens.spacing.medium
inactiveColour: Colours.palette.m3primaryContainer inactiveColour: Colours.palette.m3primaryContainer
inactiveOnColour: Colours.palette.m3onPrimaryContainer inactiveOnColour: Colours.palette.m3onPrimaryContainer
verticalPadding: Tokens.padding.small verticalPadding: Tokens.padding.extraSmall
text: qsTr("Open settings") text: qsTr("Open settings")
icon: "settings" icon: "settings"
@ -185,8 +189,8 @@ ColumnLayout {
property alias toggle: toggle property alias toggle: toggle
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -33,9 +33,7 @@ Item {
} }
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {}
type: Anim.DefaultSpatial
}
} }
Behavior on x { Behavior on x {

View file

@ -14,8 +14,8 @@ Item {
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
readonly property Item current: currentPopout?.item ?? null readonly property Item current: currentPopout?.item ?? null
implicitWidth: (currentPopout?.implicitWidth ?? 0) + Tokens.padding.large * 2 implicitWidth: (currentPopout?.implicitWidth ?? 0) + Tokens.padding.extraLargeIncreased
implicitHeight: (currentPopout?.implicitHeight ?? 0) + Tokens.padding.large * 2 implicitHeight: (currentPopout?.implicitHeight ?? 0) + Tokens.padding.extraLargeIncreased
Item { Item {
id: content id: content
@ -172,7 +172,6 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
opacity: 0 opacity: 0
scale: 0.8
active: false active: false
states: State { states: State {
@ -182,7 +181,6 @@ Item {
PropertyChanges { PropertyChanges {
popout.active: true popout.active: true
popout.opacity: 1 popout.opacity: 1
popout.scale: 1
} }
} }
@ -193,11 +191,10 @@ Item {
SequentialAnimation { SequentialAnimation {
Anim { Anim {
properties: "opacity,scale" property: "opacity"
type: Anim.StandardSmall type: Anim.DefaultEffects
} }
PropertyAction { PropertyAction {
target: popout
property: "active" property: "active"
} }
} }
@ -208,11 +205,11 @@ Item {
SequentialAnimation { SequentialAnimation {
PropertyAction { PropertyAction {
target: popout
property: "active" property: "active"
} }
Anim { Anim {
properties: "opacity,scale" property: "opacity"
type: Anim.SlowEffects
} }
} }
} }

View file

@ -26,10 +26,10 @@ ColumnLayout {
StyledText { StyledText {
visible: root.view === "wireless" visible: root.view === "wireless"
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
Layout.topMargin: visible ? Tokens.padding.normal : 0 Layout.topMargin: visible ? Tokens.padding.medium : 0
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
text: qsTr("Wireless") text: qsTr("Wireless")
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
} }
Toggle { Toggle {
@ -44,10 +44,10 @@ ColumnLayout {
visible: root.view === "wireless" visible: root.view === "wireless"
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
Layout.topMargin: visible ? Tokens.spacing.small : 0 Layout.topMargin: visible ? Tokens.spacing.small : 0
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
text: qsTr("%1 networks available").arg(Nmcli.networks.length) // qmllint disable missing-property text: qsTr("%1 networks available").arg(Nmcli.networks.length) // qmllint disable missing-property
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
Repeater { Repeater {
@ -70,7 +70,7 @@ ColumnLayout {
visible: root.view === "wireless" visible: root.view === "wireless"
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
opacity: 0 opacity: 0
@ -82,7 +82,9 @@ ColumnLayout {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -97,22 +99,22 @@ ColumnLayout {
MaterialIcon { MaterialIcon {
visible: networkItem.modelData.isSecure visible: networkItem.modelData.isSecure
text: "lock" text: "lock"
font.pointSize: Tokens.font.size.small fontStyle: Tokens.font.icon.small
} }
StyledText { StyledText {
Layout.leftMargin: Tokens.spacing.small / 2 Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.small / 2 Layout.rightMargin: Tokens.spacing.extraSmall
Layout.fillWidth: true Layout.fillWidth: true
text: networkItem.modelData.ssid text: networkItem.modelData.ssid
elide: Text.ElideRight elide: Text.ElideRight
font.weight: networkItem.modelData.active ? 500 : 400 font: Tokens.font.body.builders.medium.weight(networkItem.modelData.active ? Font.Medium : Font.Normal).build()
color: networkItem.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurface color: networkItem.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurface
} }
StyledRect { StyledRect {
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: wirelessConnectIcon.implicitHeight + Tokens.padding.small implicitHeight: wirelessConnectIcon.implicitHeight + Tokens.padding.extraSmall
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3primary, networkItem.modelData.active ? 1 : 0) color: Qt.alpha(Colours.palette.m3primary, networkItem.modelData.active ? 1 : 0)
@ -155,7 +157,9 @@ ColumnLayout {
opacity: networkItem.loading ? 0 : 1 opacity: networkItem.loading ? 0 : 1
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
} }
@ -167,7 +171,7 @@ ColumnLayout {
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
Layout.topMargin: visible ? Tokens.spacing.small : 0 Layout.topMargin: visible ? Tokens.spacing.small : 0
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: rescanBtn.implicitHeight + Tokens.padding.small * 2 implicitHeight: rescanBtn.implicitHeight + Tokens.padding.small
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Colours.palette.m3primaryContainer color: Colours.palette.m3primaryContainer
@ -201,15 +205,17 @@ ColumnLayout {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
CircularIndicator { CircularIndicator {
anchors.centerIn: parent anchors.centerIn: parent
strokeWidth: Tokens.padding.small / 2 strokeWidth: Tokens.padding.extraSmall / 2
bgColour: "transparent" bgColour: "transparent"
implicitSize: parent.implicitHeight - Tokens.padding.smaller * 2 implicitSize: parent.implicitHeight - Tokens.padding.large
running: Nmcli.scanning running: Nmcli.scanning
} }
} }
@ -218,20 +224,20 @@ ColumnLayout {
StyledText { StyledText {
visible: root.view === "ethernet" visible: root.view === "ethernet"
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
Layout.topMargin: visible ? Tokens.padding.normal : 0 Layout.topMargin: visible ? Tokens.padding.medium : 0
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
text: qsTr("Ethernet") text: qsTr("Ethernet")
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
} }
StyledText { StyledText {
visible: root.view === "ethernet" visible: root.view === "ethernet"
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
Layout.topMargin: visible ? Tokens.spacing.small : 0 Layout.topMargin: visible ? Tokens.spacing.small : 0
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
text: qsTr("%1 devices available").arg(Nmcli.ethernetDevices.length) text: qsTr("%1 devices available").arg(Nmcli.ethernetDevices.length)
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
Repeater { Repeater {
@ -253,7 +259,7 @@ ColumnLayout {
visible: root.view === "ethernet" visible: root.view === "ethernet"
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
opacity: 0 opacity: 0
@ -265,7 +271,9 @@ ColumnLayout {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -278,18 +286,18 @@ ColumnLayout {
} }
StyledText { StyledText {
Layout.leftMargin: Tokens.spacing.small / 2 Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.small / 2 Layout.rightMargin: Tokens.spacing.extraSmall
Layout.fillWidth: true Layout.fillWidth: true
text: ethernetItem.modelData.interface || qsTr("Unknown") text: ethernetItem.modelData.interface || qsTr("Unknown")
elide: Text.ElideRight elide: Text.ElideRight
font.weight: ethernetItem.modelData.connected ? 500 : 400 font: Tokens.font.body.builders.medium.weight(ethernetItem.modelData.connected ? Font.Medium : Font.Normal).build()
color: ethernetItem.modelData.connected ? Colours.palette.m3primary : Colours.palette.m3onSurface color: ethernetItem.modelData.connected ? Colours.palette.m3primary : Colours.palette.m3onSurface
} }
StyledRect { StyledRect {
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: connectIcon.implicitHeight + Tokens.padding.small implicitHeight: connectIcon.implicitHeight + Tokens.padding.extraSmall
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3primary, ethernetItem.modelData.connected ? 1 : 0) color: Qt.alpha(Colours.palette.m3primary, ethernetItem.modelData.connected ? 1 : 0)
@ -323,7 +331,9 @@ ColumnLayout {
opacity: ethernetItem.loading ? 0 : 1 opacity: ethernetItem.loading ? 0 : 1
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
} }
@ -371,8 +381,8 @@ ColumnLayout {
property alias toggle: toggle property alias toggle: toggle
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -45,7 +45,7 @@ StackView {
property bool isSubMenu property bool isSubMenu
property bool shown property bool shown
padding: Tokens.padding.smaller padding: Tokens.padding.small
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
opacity: shown ? 1 : 0 opacity: shown ? 1 : 0
@ -57,7 +57,9 @@ StackView {
StackView.onRemoved: destroy() StackView.onRemoved: destroy()
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -97,9 +99,9 @@ StackView {
implicitHeight: label.implicitHeight implicitHeight: label.implicitHeight
StateLayer { StateLayer {
anchors.margins: -Tokens.padding.small / 2 anchors.margins: -Tokens.padding.extraSmall / 2
anchors.leftMargin: -Tokens.padding.smaller anchors.leftMargin: -Tokens.padding.small
anchors.rightMargin: -Tokens.padding.smaller anchors.rightMargin: -Tokens.padding.small
radius: item.radius radius: item.radius
disabled: !item.modelData.enabled disabled: !item.modelData.enabled
@ -138,7 +140,7 @@ StackView {
id: label id: label
anchors.left: icon.right anchors.left: icon.right
anchors.leftMargin: icon.active ? Tokens.spacing.smaller : 0 anchors.leftMargin: icon.active ? Tokens.spacing.medium : 0
text: labelMetrics.elidedText text: labelMetrics.elidedText
color: item.modelData.enabled ? Colours.palette.m3onSurface : Colours.palette.m3outline color: item.modelData.enabled ? Colours.palette.m3onSurface : Colours.palette.m3outline
@ -148,11 +150,10 @@ StackView {
id: labelMetrics id: labelMetrics
text: item.modelData.text text: item.modelData.text
font.pointSize: label.font.pointSize font: label.font
font.family: label.font.family
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: root.Tokens.sizes.bar.trayMenuWidth - (icon.active ? icon.implicitWidth + label.anchors.leftMargin : 0) - (expand.active ? expand.implicitWidth + root.Tokens.spacing.normal : 0) elideWidth: root.Tokens.sizes.bar.trayMenuWidth - (icon.active ? icon.implicitWidth + label.anchors.leftMargin : 0) - (expand.active ? expand.implicitWidth + root.Tokens.spacing.medium : 0)
} }
Loader { Loader {
@ -180,7 +181,7 @@ StackView {
sourceComponent: Item { sourceComponent: Item {
implicitWidth: back.implicitWidth implicitWidth: back.implicitWidth
implicitHeight: back.implicitHeight + Tokens.spacing.small / 2 implicitHeight: back.implicitHeight + Tokens.spacing.extraSmall
Item { Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -189,9 +190,9 @@ StackView {
StyledRect { StyledRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: -Tokens.padding.small / 2 anchors.margins: -Tokens.padding.extraSmall / 2
anchors.leftMargin: -Tokens.padding.smaller anchors.leftMargin: -Tokens.padding.small
anchors.rightMargin: -Tokens.padding.smaller * 2 anchors.rightMargin: -Tokens.padding.large
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Colours.palette.m3secondaryContainer color: Colours.palette.m3secondaryContainer

View file

@ -69,9 +69,9 @@ ColumnLayout {
} }
} }
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
implicitWidth: 400 implicitWidth: 400
implicitHeight: content.implicitHeight + Tokens.padding.large * 2 implicitHeight: content.implicitHeight + Tokens.padding.extraLargeIncreased
visible: shouldBeVisible || isClosing visible: shouldBeVisible || isClosing
enabled: shouldBeVisible && !isClosing enabled: shouldBeVisible && !isClosing
focus: enabled focus: enabled
@ -128,8 +128,8 @@ ColumnLayout {
StyledRect { StyledRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: 400 Layout.preferredWidth: 400
implicitHeight: content.implicitHeight + Tokens.padding.large * 2 implicitHeight: content.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
visible: root.shouldBeVisible || root.isClosing visible: root.shouldBeVisible || root.isClosing
opacity: root.shouldBeVisible && !root.isClosing ? 1 : 0 opacity: root.shouldBeVisible && !root.isClosing ? 1 : 0
@ -137,7 +137,9 @@ ColumnLayout {
Keys.onEscapePressed: root.closeDialog() Keys.onEscapePressed: root.closeDialog()
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -153,6 +155,7 @@ ColumnLayout {
} }
Anim { Anim {
type: Anim.DefaultEffects
target: parent target: parent
property: "opacity" property: "opacity"
to: 0 to: 0
@ -172,19 +175,18 @@ ColumnLayout {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "lock" text: "lock"
font.pointSize: Tokens.font.size.extraLarge * 2 fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).build()
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("Enter password") text: qsTr("Enter password")
font.pointSize: Tokens.font.size.large font: Tokens.font.body.builders.large.weight(Font.Medium).build()
font.weight: 500
} }
StyledText { StyledText {
@ -201,7 +203,7 @@ ColumnLayout {
return qsTr("Network: Unknown"); return qsTr("Network: Unknown");
} }
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
} }
Timer { Timer {
@ -249,10 +251,9 @@ ColumnLayout {
return ""; return "";
} }
color: connectButton.hasError ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant color: connectButton.hasError ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small font: Tokens.font.body.builders.small.weight(Font.Normal).build()
font.weight: 400
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.maximumWidth: parent.width - Tokens.padding.large * 2 Layout.maximumWidth: parent.width - Tokens.padding.extraLargeIncreased
} }
FocusScope { FocusScope {
@ -261,9 +262,9 @@ ColumnLayout {
property string passwordBuffer: "" property string passwordBuffer: ""
objectName: "passwordContainer" objectName: "passwordContainer"
Layout.topMargin: Tokens.spacing.large Layout.topMargin: Tokens.spacing.largeIncreased
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: Math.max(48, charList.implicitHeight + Tokens.padding.normal * 2) implicitHeight: Math.max(48, charList.implicitHeight + Tokens.padding.medium * 2)
focus: true focus: true
activeFocusOnTab: true activeFocusOnTab: true
@ -336,7 +337,7 @@ ColumnLayout {
StyledRect { StyledRect {
anchors.fill: parent anchors.fill: parent
radius: Tokens.rounding.normal radius: Tokens.rounding.large
color: passwordContainer.activeFocus ? Qt.lighter(Colours.tPalette.m3surfaceContainer, 1.05) : Colours.tPalette.m3surfaceContainer color: passwordContainer.activeFocus ? Qt.lighter(Colours.tPalette.m3surfaceContainer, 1.05) : Colours.tPalette.m3surfaceContainer
border.width: passwordContainer.activeFocus || connectButton.hasError ? 4 : (root.shouldBeVisible ? 1 : 0) border.width: passwordContainer.activeFocus || connectButton.hasError ? 4 : (root.shouldBeVisible ? 1 : 0)
border.color: { border.color: {
@ -365,7 +366,7 @@ ColumnLayout {
StateLayer { StateLayer {
hoverEnabled: false hoverEnabled: false
cursorShape: Qt.IBeamCursor cursorShape: Qt.IBeamCursor
radius: Tokens.rounding.normal radius: Tokens.rounding.large
onClicked: passwordContainer.forceActiveFocus() onClicked: passwordContainer.forceActiveFocus()
} }
@ -375,12 +376,13 @@ ColumnLayout {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("Password") text: qsTr("Password")
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Tokens.font.size.normal font: Tokens.font.mono.medium
font.family: Tokens.font.family.mono
opacity: passwordContainer.passwordBuffer ? 0 : 1 opacity: passwordContainer.passwordBuffer ? 0 : 1
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
} }
@ -391,10 +393,10 @@ ColumnLayout {
anchors.centerIn: parent anchors.centerIn: parent
implicitWidth: fullWidth implicitWidth: fullWidth
implicitHeight: Tokens.font.size.normal implicitHeight: Tokens.font.body.medium.pointSize
orientation: Qt.Horizontal orientation: Qt.Horizontal
spacing: Tokens.spacing.small / 2 spacing: Tokens.spacing.extraSmall
interactive: false interactive: false
model: ScriptModel { model: ScriptModel {
@ -408,7 +410,7 @@ ColumnLayout {
implicitHeight: charList.implicitHeight implicitHeight: charList.implicitHeight
color: Colours.palette.m3onSurface color: Colours.palette.m3onSurface
radius: Tokens.rounding.small / 2 radius: Tokens.rounding.medium / 2
opacity: 0 opacity: 0
scale: 0 scale: 0
@ -428,6 +430,7 @@ ColumnLayout {
} }
ParallelAnimation { ParallelAnimation {
Anim { Anim {
type: Anim.DefaultEffects
target: ch target: ch
property: "opacity" property: "opacity"
to: 0 to: 0
@ -446,7 +449,9 @@ ColumnLayout {
} }
Behavior on opacity { Behavior on opacity {
Anim {} Anim {
type: Anim.DefaultEffects
}
} }
Behavior on scale { Behavior on scale {
@ -463,15 +468,15 @@ ColumnLayout {
} }
RowLayout { RowLayout {
Layout.topMargin: Tokens.spacing.normal Layout.topMargin: Tokens.spacing.medium
Layout.fillWidth: true Layout.fillWidth: true
spacing: Tokens.spacing.normal spacing: Tokens.spacing.medium
TextButton { TextButton {
id: cancelButton id: cancelButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: Tokens.font.size.normal + Tokens.padding.normal * 2 Layout.minimumHeight: Tokens.font.body.medium.pointSize + Tokens.padding.medium * 2
inactiveColour: Colours.palette.m3secondaryContainer inactiveColour: Colours.palette.m3secondaryContainer
inactiveOnColour: Colours.palette.m3onSecondaryContainer inactiveOnColour: Colours.palette.m3onSecondaryContainer
text: qsTr("Cancel") text: qsTr("Cancel")
@ -486,7 +491,7 @@ ColumnLayout {
property bool hasError: false property bool hasError: false
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: Tokens.font.size.normal + Tokens.padding.normal * 2 Layout.minimumHeight: Tokens.font.body.medium.pointSize + Tokens.padding.medium * 2
inactiveColour: Colours.palette.m3primary inactiveColour: Colours.palette.m3primary
inactiveOnColour: Colours.palette.m3onPrimary inactiveOnColour: Colours.palette.m3onPrimary
text: qsTr("Connect") text: qsTr("Connect")

View file

@ -7,7 +7,7 @@ import Quickshell.Wayland
import Caelestia.Config import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
import qs.modules.controlcenter import qs.modules.nexus
import qs.modules.windowinfo import qs.modules.windowinfo
Item { Item {
@ -18,7 +18,7 @@ Item {
readonly property alias content: content readonly property alias content: content
readonly property alias winfo: winfo readonly property alias winfo: winfo
readonly property alias controlCenter: controlCenter readonly property alias nexus: nexus
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
@ -128,17 +128,26 @@ Item {
} }
Comp { Comp {
id: controlCenter id: nexus
shouldBeActive: root.detachedMode === "any" shouldBeActive: root.detachedMode === "any"
anchors.centerIn: parent anchors.centerIn: parent
sourceComponent: ControlCenter { sourceComponent: StyledClippingRect {
screen: root.screen radius: Tokens.rounding.extraLarge
active: root.queuedMode implicitWidth: nexusInner.implicitWidth
implicitHeight: nexusInner.implicitHeight
Nexus {
id: nexusInner
anchors.fill: parent
nState.screen: root.screen
nState.currentPageIdx: ["appearance", "network", "bluetooth", "audio"].indexOf(root.queuedMode)
onClose: root.close() onClose: root.close()
} }
} }
}
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
@ -185,6 +194,7 @@ Item {
property: "active" property: "active"
} }
Anim { Anim {
type: Anim.DefaultEffects
property: "opacity" property: "opacity"
} }
} }
@ -195,6 +205,7 @@ Item {
SequentialAnimation { SequentialAnimation {
Anim { Anim {
type: Anim.DefaultEffects
property: "opacity" property: "opacity"
} }
PropertyAction { PropertyAction {

View file

@ -24,10 +24,10 @@ ColumnLayout {
} }
StyledText { StyledText {
Layout.topMargin: Tokens.padding.normal Layout.topMargin: Tokens.padding.medium
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
text: qsTr("Keyboard Layouts") text: qsTr("Keyboard Layouts")
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
} }
ListView { ListView {
@ -36,7 +36,7 @@ ColumnLayout {
model: kb.visibleModel model: kb.visibleModel
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
Layout.topMargin: Tokens.spacing.small Layout.topMargin: Tokens.spacing.small
clip: true clip: true
@ -88,7 +88,7 @@ ColumnLayout {
readonly property bool isDisabled: layoutIndex > 3 readonly property bool isDisabled: layoutIndex > 3
width: list.width width: list.width
height: Math.max(36, rowText.implicitHeight + Tokens.padding.small * 2) height: Math.max(36, rowText.implicitHeight + Tokens.padding.small)
ToolTip.visible: isDisabled && layer.containsMouse ToolTip.visible: isDisabled && layer.containsMouse
ToolTip.text: "XKB limitation: maximum 4 layouts allowed" ToolTip.text: "XKB limitation: maximum 4 layouts allowed"
@ -114,8 +114,8 @@ ColumnLayout {
anchors.verticalCenter: layer.verticalCenter anchors.verticalCenter: layer.verticalCenter
anchors.left: layer.left anchors.left: layer.left
anchors.right: layer.right anchors.right: layer.right
anchors.leftMargin: Tokens.padding.small anchors.leftMargin: Tokens.padding.extraSmall
anchors.rightMargin: Tokens.padding.small anchors.rightMargin: Tokens.padding.extraSmall
text: kbDelegate.label text: kbDelegate.label
elide: Text.ElideRight elide: Text.ElideRight
opacity: kbDelegate.isDisabled ? 0.4 : 1.0 opacity: kbDelegate.isDisabled ? 0.4 : 1.0
@ -126,7 +126,7 @@ ColumnLayout {
Rectangle { Rectangle {
visible: kb.activeLabel.length > 0 visible: kb.activeLabel.length > 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
Layout.topMargin: Tokens.spacing.small Layout.topMargin: Tokens.spacing.small
implicitHeight: 1 implicitHeight: 1
@ -139,7 +139,7 @@ ColumnLayout {
visible: kb.activeLabel.length > 0 visible: kb.activeLabel.length > 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.extraSmall
Layout.topMargin: Tokens.spacing.small Layout.topMargin: Tokens.spacing.small
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
@ -155,7 +155,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
text: kb.activeLabel text: kb.activeLabel
elide: Text.ElideRight elide: Text.ElideRight
font.weight: 500 font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
color: Colours.palette.m3primary color: Colours.palette.m3primary
} }

View file

@ -10,25 +10,25 @@ import Caelestia.Config
Item { Item {
id: model id: model
property alias visibleModel: _visibleModel property alias visibleModel: visibleModel
property string activeLabel: "" property string activeLabel: ""
property int activeIndex: -1 property int activeIndex: -1
property var _xkbMap: ({}) property var _xkbMap: ({})
property bool _notifiedLimit: false property bool _notifiedLimit: false
function start() { function start() {
_xkbXmlBase.running = true; xkbXmlBase.running = true;
_getKbLayoutOpt.running = true; getKbLayoutOpt.running = true;
} }
function refresh() { function refresh() {
_notifiedLimit = false; _notifiedLimit = false;
_getKbLayoutOpt.running = true; getKbLayoutOpt.running = true;
} }
function switchTo(idx) { function switchTo(idx) {
_switchProc.command = ["hyprctl", "switchxkblayout", "all", String(idx)]; switchProc.command = ["hyprctl", "switchxkblayout", "all", String(idx)];
_switchProc.running = true; switchProc.running = true;
} }
function _buildXmlMap(xml) { function _buildXmlMap(xml) {
@ -50,19 +50,19 @@ Item {
_xkbMap = map; _xkbMap = map;
if (_layoutsModel.count > 0) { if (layoutsModel.count > 0) {
const tmp = []; const tmp = [];
for (let i = 0; i < _layoutsModel.count; i++) { for (let i = 0; i < layoutsModel.count; i++) {
const it = _layoutsModel.get(i); const it = layoutsModel.get(i);
tmp.push({ tmp.push({
layoutIndex: it.layoutIndex, layoutIndex: it.layoutIndex,
token: it.token, token: it.token,
label: _pretty(it.token) label: _pretty(it.token)
}); });
} }
_layoutsModel.clear(); layoutsModel.clear();
tmp.forEach(t => _layoutsModel.append(t)); tmp.forEach(t => layoutsModel.append(t));
_fetchActiveLayouts.running = true; fetchActiveLayouts.running = true;
} }
} }
@ -78,7 +78,7 @@ Item {
function _setLayouts(raw) { function _setLayouts(raw) {
const parts = raw.split(",").map(s => s.trim()).filter(Boolean); const parts = raw.split(",").map(s => s.trim()).filter(Boolean);
_layoutsModel.clear(); layoutsModel.clear();
const seen = new Set(); const seen = new Set();
let idx = 0; let idx = 0;
@ -87,7 +87,7 @@ Item {
if (seen.has(p)) if (seen.has(p))
continue; continue;
seen.add(p); seen.add(p);
_layoutsModel.append({ layoutsModel.append({
layoutIndex: idx, layoutIndex: idx,
token: p, token: p,
label: _pretty(p) label: _pretty(p)
@ -97,19 +97,19 @@ Item {
} }
function _rebuildVisible() { function _rebuildVisible() {
_visibleModel.clear(); visibleModel.clear();
let arr = []; let arr = [];
for (let i = 0; i < _layoutsModel.count; i++) for (let i = 0; i < layoutsModel.count; i++)
arr.push(_layoutsModel.get(i)); arr.push(layoutsModel.get(i));
arr = arr.filter(i => i.layoutIndex !== activeIndex); arr = arr.filter(i => i.layoutIndex !== activeIndex);
arr.forEach(i => _visibleModel.append(i)); arr.forEach(i => visibleModel.append(i));
if (!GlobalConfig.utilities.toasts.kbLimit) if (!GlobalConfig.utilities.toasts.kbLimit)
return; return;
if (_layoutsModel.count > 4) { if (layoutsModel.count > 4) {
Toaster.toast(qsTr("Keyboard layout limit"), qsTr("XKB supports only 4 layouts at a time"), "warning"); Toaster.toast(qsTr("Keyboard layout limit"), qsTr("XKB supports only 4 layouts at a time"), "warning");
} }
} }
@ -124,26 +124,26 @@ Item {
visible: false visible: false
ListModel { ListModel {
id: _visibleModel id: visibleModel
} }
ListModel { ListModel {
id: _layoutsModel id: layoutsModel
} }
Process { Process {
id: _xkbXmlBase id: xkbXmlBase
command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/base.xml"] command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/base.xml"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: model._buildXmlMap(text) onStreamFinished: model._buildXmlMap(text)
} }
onRunningChanged: if (!running && (typeof _xkbXmlBase.exitCode !== "undefined") && _xkbXmlBase.exitCode !== 0) // qmllint disable missing-property onRunningChanged: if (!running && (typeof xkbXmlBase.exitCode !== "undefined") && xkbXmlBase.exitCode !== 0) // qmllint disable missing-property
_xkbXmlEvdev.running = true xkbXmlEvdev.running = true
} }
Process { Process {
id: _xkbXmlEvdev id: xkbXmlEvdev
command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/evdev.xml"] command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/evdev.xml"]
stdout: StdioCollector { stdout: StdioCollector {
@ -152,7 +152,7 @@ Item {
} }
Process { Process {
id: _getKbLayoutOpt id: getKbLayoutOpt
command: ["hyprctl", "-j", "getoption", "input:kb_layout"] command: ["hyprctl", "-j", "getoption", "input:kb_layout"]
stdout: StdioCollector { stdout: StdioCollector {
@ -162,17 +162,17 @@ Item {
const raw = (j?.str || j?.value || "").toString().trim(); const raw = (j?.str || j?.value || "").toString().trim();
if (raw.length) { if (raw.length) {
model._setLayouts(raw); model._setLayouts(raw);
_fetchActiveLayouts.running = true; fetchActiveLayouts.running = true;
return; return;
} }
} catch (e) {} } catch (e) {}
_fetchLayoutsFromDevices.running = true; fetchLayoutsFromDevices.running = true;
} }
} }
} }
Process { Process {
id: _fetchLayoutsFromDevices id: fetchLayoutsFromDevices
command: ["hyprctl", "-j", "devices"] command: ["hyprctl", "-j", "devices"]
stdout: StdioCollector { stdout: StdioCollector {
@ -184,13 +184,13 @@ Item {
if (raw.length) if (raw.length)
model._setLayouts(raw); model._setLayouts(raw);
} catch (e) {} } catch (e) {}
_fetchActiveLayouts.running = true; fetchActiveLayouts.running = true;
} }
} }
} }
Process { Process {
id: _fetchActiveLayouts id: fetchActiveLayouts
command: ["hyprctl", "-j", "devices"] command: ["hyprctl", "-j", "devices"]
stdout: StdioCollector { stdout: StdioCollector {
@ -201,7 +201,7 @@ Item {
const idx = kb?.active_layout_index ?? -1; const idx = kb?.active_layout_index ?? -1;
model.activeIndex = idx >= 0 ? idx : -1; model.activeIndex = idx >= 0 ? idx : -1;
model.activeLabel = (idx >= 0 && idx < _layoutsModel.count) ? _layoutsModel.get(idx).label : ""; model.activeLabel = (idx >= 0 && idx < layoutsModel.count) ? layoutsModel.get(idx).label : "";
} catch (e) { } catch (e) {
model.activeIndex = -1; model.activeIndex = -1;
model.activeLabel = ""; model.activeLabel = "";
@ -213,9 +213,9 @@ Item {
} }
Process { Process {
id: _switchProc id: switchProc
onRunningChanged: if (!running) onRunningChanged: if (!running)
_fetchActiveLayouts.running = true fetchActiveLayouts.running = true
} }
} }

View file

@ -1,99 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
Item {
id: root
required property ShellScreen screen
readonly property int rounding: floating ? 0 : Tokens.rounding.large
property alias floating: session.floating
property alias active: session.active
property alias navExpanded: session.navExpanded
readonly property bool initialOpeningComplete: panes.initialOpeningComplete
readonly property Session session: Session {
id: session
root: root
}
signal close
implicitWidth: implicitHeight * Tokens.sizes.controlCenter.ratio
implicitHeight: screen.height * Tokens.sizes.controlCenter.heightMult
GridLayout {
anchors.fill: parent
rowSpacing: 0
columnSpacing: 0
rows: root.floating ? 2 : 1
columns: 2
Loader {
Layout.fillWidth: true
Layout.columnSpan: 2
asynchronous: true
active: root.floating
visible: active
sourceComponent: WindowTitle {
screen: root.screen
session: root.session
}
}
StyledRect {
Layout.fillHeight: true
topLeftRadius: root.rounding
bottomLeftRadius: root.rounding
implicitWidth: navRail.implicitWidth
color: Colours.tPalette.m3surfaceContainer
CustomMouseArea {
function onWheel(event: WheelEvent): void {
// Prevent tab switching during initial opening animation to avoid blank pages
if (!panes.initialOpeningComplete) {
return;
}
if (event.angleDelta.y < 0)
root.session.activeIndex = Math.min(root.session.activeIndex + 1, root.session.panes.length - 1);
else if (event.angleDelta.y > 0)
root.session.activeIndex = Math.max(root.session.activeIndex - 1, 0);
}
anchors.fill: parent
}
NavRail {
id: navRail
screen: root.screen
session: root.session
initialOpeningComplete: root.initialOpeningComplete
}
}
Panes {
id: panes
Layout.fillWidth: true
Layout.fillHeight: true
topRightRadius: root.rounding
bottomRightRadius: root.rounding
session: root.session
}
}
}

Some files were not shown because too many files have changed in this diff Show more