Merge tag 'v2.9.2'

Release v2.9.2
This commit is contained in:
Never Gude 2025-09-16 13:24:39 +02:00
commit 7b1a5d2eb2
150 changed files with 6907 additions and 5080 deletions

View file

@ -1,7 +1,7 @@
--- ---
name: Bug Report name: Bug Report
about: Report a bug from noctalia-shell about: Report a bug from noctalia-shell
title: "[Bug]: " title: "[Bug] "
labels: bug labels: bug
assignees: '' assignees: ''
--- ---

View file

@ -1,7 +1,7 @@
--- ---
name: Feature Request name: Feature Request
about: Suggest a new feature or improvement about: Suggest a new feature or improvement
title: "[Feature]: " title: "[Feature] "
labels: enhancement labels: enhancement
assignees: '' assignees: ''
--- ---

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.qmlls.ini

View file

@ -4,7 +4,7 @@
"mOnPrimary": "#11111b", "mOnPrimary": "#11111b",
"mSecondary": "#fab387", "mSecondary": "#fab387",
"mOnSecondary": "#11111b", "mOnSecondary": "#11111b",
"mTertiary": "#a6e3a1", "mTertiary": "#94e2d5",
"mOnTertiary": "#11111b", "mOnTertiary": "#11111b",
"mError": "#f38ba8", "mError": "#f38ba8",
"mOnError": "#11111b", "mOnError": "#11111b",
@ -16,19 +16,19 @@
"mShadow": "#11111b" "mShadow": "#11111b"
}, },
"light": { "light": {
"mPrimary": "#9349ef", "mPrimary": "#8839ef",
"mOnPrimary": "#eff1f5", "mOnPrimary": "#eff1f5",
"mSecondary": "#f67525", "mSecondary": "#fe640b",
"mOnSecondary": "#eff1f5", "mOnSecondary": "#eff1f5",
"mTertiary": "#40b635", "mTertiary": "#40a02b",
"mOnTertiary": "#eff1f5", "mOnTertiary": "#eff1f5",
"mError": "#f38ba8", "mError": "#d20f39",
"mOnError": "#11111b", "mOnError": "#dce0e8",
"mSurface": "#eff1f5", "mSurface": "#eff1f5",
"mOnSurface": "#4c4f69", "mOnSurface": "#4c4f69",
"mSurfaceVariant": "#ccd0da", "mSurfaceVariant": "#ccd0da",
"mOnSurfaceVariant": "#6c6f85", "mOnSurfaceVariant": "#6c6f85",
"mOutline": "#aeb5c4", "mOutline": "#a5adcb",
"mShadow": "#dce0e8" "mShadow": "#dce0e8"
} }
} }

View file

@ -0,0 +1,34 @@
{
"dark": {
"mPrimary": "#D3C6AA",
"mOnPrimary": "#232A2E",
"mSecondary": "#D3C6AA",
"mOnSecondary": "#232A2E",
"mTertiary": "#9DA9A0",
"mOnTertiary": "#232A2E",
"mError": "#E67E80",
"mOnError": "#232A2E",
"mSurface": "#232A2E",
"mOnSurface": "#859289",
"mSurfaceVariant": "#2D353B",
"mOnSurfaceVariant": "#D3C6AA",
"mOutline": "#D3C6AA",
"mShadow": "#475258"
},
"light": {
"mPrimary": "#434F55",
"mOnPrimary": "#D3C6AA",
"mSecondary": "#232a2e",
"mOnSecondary": "#D3C6AA",
"mTertiary": "#333c43",
"mOnTertiary": "#9DA9A0",
"mError": "#E66868",
"mOnError": "#9DA9A0",
"mSurface": "#BEC5B2",
"mOnSurface": "#333C43",
"mSurfaceVariant": "#9DA9A0",
"mOnSurfaceVariant": "#232A2E",
"mOutline": "#232A2E",
"mShadow": "#ECF5ED"
}
}

View file

@ -0,0 +1,34 @@
{
"dark": {
"mPrimary": "#aaaaaa",
"mOnPrimary": "#111111",
"mSecondary": "#a7a7a7",
"mOnSecondary": "#111111",
"mTertiary": "#cccccc",
"mOnTertiary": "#111111",
"mError": "#dddddd",
"mOnError": "#111111",
"mSurface": "#111111",
"mOnSurface": "#828282",
"mSurfaceVariant": "#191919",
"mOnSurfaceVariant": "#5d5d5d",
"mOutline": "#3c3c3c",
"mShadow": "#000000"
},
"light": {
"mPrimary": "#555555",
"mOnPrimary": "#eeeeee",
"mSecondary": "#505058",
"mOnSecondary": "#eeeeee",
"mTertiary": "#333333",
"mOnTertiary": "#eeeeee",
"mError": "#222222",
"mOnError": "#efefef",
"mSurface": "#d4d4d4",
"mOnSurface": "#696969",
"mSurfaceVariant": "#e8e8e8",
"mOnSurfaceVariant": "#9e9e9e",
"mOutline": "#c3c3c3",
"mShadow": "#fafafa"
}
}

View file

@ -4,8 +4,8 @@
"mOnPrimary": "#191724", "mOnPrimary": "#191724",
"mSecondary": "#9ccfd8", "mSecondary": "#9ccfd8",
"mOnSecondary": "#191724", "mOnSecondary": "#191724",
"mTertiary": "#f6c177", "mTertiary": "#524f67",
"mOnTertiary": "#191724", "mOnTertiary": "#e0def4",
"mError": "#eb6f92", "mError": "#eb6f92",
"mOnError": "#191724", "mOnError": "#191724",
"mSurface": "#191724", "mSurface": "#191724",
@ -16,19 +16,19 @@
"mShadow": "#191724" "mShadow": "#191724"
}, },
"light": { "light": {
"mPrimary": "#d46e6b", "mPrimary": "#d7827e",
"mOnPrimary": "#faf4ed", "mOnPrimary": "#faf4ed",
"mSecondary": "#56949f", "mSecondary": "#56949f",
"mOnSecondary": "#faf4ed", "mOnSecondary": "#faf4ed",
"mTertiary": "#31748f", "mTertiary": "#cecacd",
"mOnTertiary": "#232136", "mOnTertiary": "#575279",
"mError": "#b4637a", "mError": "#b4637a",
"mOnError": "#f2e9e1", "mOnError": "#faf4ed",
"mSurface": "#e0def4", "mSurface": "#faf4ed",
"mOnSurface": "#232136", "mOnSurface": "#575279",
"mSurfaceVariant": "#bcb8e7", "mSurfaceVariant": "#f2e9e1",
"mOnSurfaceVariant": "#797593", "mOnSurfaceVariant": "#797593",
"mOutline": "#9893a5", "mOutline": "#dfdad9",
"mShadow": "#575279" "mShadow": "#faf4ed"
} }
} }

View file

@ -1,34 +1,34 @@
{ {
"dark": { "dark": {
"mPrimary": "#ff9e64", "mPrimary": "#7aa2f7",
"mOnPrimary": "#1a1b26", "mOnPrimary": "#16161e",
"mSecondary": "#e0af68", "mSecondary": "#bb9af7",
"mOnSecondary": "#1a1b26", "mOnSecondary": "#16161e",
"mTertiary": "#7aa2f7", "mTertiary": "#9ece6a",
"mOnTertiary": "#1a1b26", "mOnTertiary": "#16161e",
"mError": "#f7768e", "mError": "#f7768e",
"mOnError": "#1a1b26", "mOnError": "#16161e",
"mSurface": "#1a1b26", "mSurface": "#1a1b26",
"mOnSurface": "#a9b1d6", "mOnSurface": "#c0caf5",
"mSurfaceVariant": "#292e42", "mSurfaceVariant": "#24283b",
"mOnSurfaceVariant": "#787c99", "mOnSurfaceVariant": "#9aa5ce",
"mOutline": "#3d4462", "mOutline": "#565f89",
"mShadow": "#1a1b26" "mShadow": "#15161e"
}, },
"light": { "light": {
"mPrimary": "#fd5d00", "mPrimary": "#2e7de9",
"mOnPrimary": "#e6e7ed", "mOnPrimary": "#e1e2e7",
"mSecondary": "#bb8027", "mSecondary": "#9854f1",
"mOnSecondary": "#e6e7ed", "mOnSecondary": "#e1e2e7",
"mTertiary": "#4a80f4", "mTertiary": "#587539",
"mOnTertiary": "#e6e7ed", "mOnTertiary": "#e1e2e7",
"mError": "#965027", "mError": "#f52a65",
"mOnError": "#e6e7ed", "mOnError": "#e1e2e7",
"mSurface": "#e6e7ed", "mSurface": "#e1e2e7",
"mOnSurface": "#343b58", "mOnSurface": "#3760bf",
"mSurfaceVariant": "#d5d6db", "mSurfaceVariant": "#d0d5e3",
"mOnSurfaceVariant": "#40434f", "mOnSurfaceVariant": "#6172b0",
"mOutline": "#babbc3", "mOutline": "#b4b5b9",
"mShadow": "#c0caf5" "mShadow": "#a8aecb"
} }
} }

Binary file not shown.

View file

@ -51,28 +51,31 @@ Singleton {
lines.push("\n[templates.ghostty]") lines.push("\n[templates.ghostty]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/ghostty.conf"') lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/ghostty.conf"')
lines.push('output_path = "~/.config/ghostty/themes/noctalia"') lines.push('output_path = "~/.config/ghostty/themes/noctalia"')
lines.push( lines.push("post_hook = \"grep -q '^theme *= *' ~/.config/ghostty/config; and sed -i 's/^theme *= *.*/theme = noctalia/' ~/.config/ghostty/config; or echo 'theme = noctalia' >> ~/.config/ghostty/config\"")
"post_hook = \"grep -q '^theme *= *' ~/.config/ghostty/config; and sed -i 's/^theme *= *.*/theme = noctalia/' ~/.config/ghostty/config; or echo 'theme = noctalia' >> ~/.config/ghostty/config\"")
} }
if (Settings.data.matugen.foot) { if (Settings.data.matugen.foot) {
lines.push("\n[templates.foot]") lines.push("\n[templates.foot]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/foot.conf"') lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/foot.conf"')
lines.push('output_path = "~/.config/foot/themes/noctalia"') lines.push('output_path = "~/.config/foot/themes/noctalia"')
lines.push( lines.push('post_hook = "sed -i /themes/d ~/.config/foot/foot.ini && echo include=~/.config/foot/themes/noctalia >> ~/.config/foot/foot.ini"')
'post_hook = "sed -i /themes/d ~/.config/foot/foot.ini && echo include=~/.config/foot/themes/noctalia >> ~/.config/foot/foot.ini"')
} }
if (Settings.data.matugen.fuzzel) { if (Settings.data.matugen.fuzzel) {
lines.push("\n[templates.fuzzel]") lines.push("\n[templates.fuzzel]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/fuzzel.conf"') lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/fuzzel.conf"')
lines.push('output_path = "~/.config/fuzzel/themes/noctalia"') lines.push('output_path = "~/.config/fuzzel/themes/noctalia"')
lines.push( lines.push('post_hook = "sed -i /themes/d ~/.config/fuzzel/fuzzel.ini && echo include=~/.config/fuzzel/themes/noctalia >> ~/.config/fuzzel/fuzzel.ini"')
'post_hook = "sed -i /themes/d ~/.config/fuzzel/fuzzel.ini && echo include=~/.config/fuzzel/themes/noctalia >> ~/.config/fuzzel/fuzzel.ini"')
} }
if (Settings.data.matugen.vesktop) { if (Settings.data.matugen.vesktop) {
lines.push("\n[templates.vesktop]") lines.push("\n[templates.vesktop]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/vesktop.css"') lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/vesktop.css"')
lines.push('output_path = "~/.config/vesktop/themes/noctalia.theme.css"') lines.push('output_path = "~/.config/vesktop/themes/noctalia.theme.css"')
} }
if (Settings.data.matugen.pywalfox) {
lines.push("\n[templates.pywalfox]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/pywalfox.json"')
lines.push('output_path = "~/.cache/wal/colors.json"')
lines.push('post_hook = "pywalfox update"')
}
return lines.join("\n") + "\n" return lines.join("\n") + "\n"
} }

View file

@ -0,0 +1,22 @@
{
"wallpaper": "{{image}}",
"alpha": "100",
"colors": {
"color0": "{{colors.background.default.hex}}",
"color1": "",
"color2": "",
"color3": "",
"color4": "",
"color5": "",
"color6": "",
"color7": "",
"color8": "",
"color9": "",
"color10": "{{colors.primary.default.hex}}",
"color11": "",
"color12": "",
"color13": "{{colors.surface_bright.default.hex}}",
"color14": "",
"color15": "{{colors.on_surface.default.hex}}"
}
}

View file

@ -1,572 +1,113 @@
/* /**
* Vesktop Theme * @name noctalia
* Generated with Matugen * @description Original theme: midnight | A dark, rounded discord theme.
* Base was taken from https://github.com/catppuccin/discord <3 * @author refact0r
* @version 1.6.2
* @invite nz87hXyvcy
* @website https://github.com/refact0r/midnight-discord
* @source https://github.com/refact0r/midnight-discord/blob/master/midnight.theme.css
* @authorId 508863359777505290
* @authorLink https://www.refact0r.dev
*/ */
/* IMPORTANT: make sure to enable dark mode in discord settings for the theme to apply properly!!! */
/* Dark Theme */ @import url('https://refact0r.github.io/midnight-discord/build/midnight.css');
.visual-refresh.theme-dark,
.visual-refresh .theme-dark {
/* Brand Colors */
--brand-experiment: {{colors.primary.default.hex}};
--bg-brand: {{colors.primary.default.hex}};
--brand-500: {{colors.primary.default.hex}} !important;
--text-link: {{colors.primary.default.hex}} !important;
--text-brand: {{colors.primary.default.hex}};
--control-brand-foreground: {{colors.primary.default.hex}};
--control-brand-foreground-new: {{colors.primary.default.hex}};
--mention-foreground: {{colors.primary.default.hex}};
--mention-background: {{colors.primary.default.hex}}20;
--focus-primary: {{colors.primary.default.hex}};
--logo-primary: {{colors.on_surface.default.hex}};
--badge-brand-bg: {{colors.primary.default.hex}};
--badge-brand-text: {{colors.on_primary.default.hex}};
/* Text Colors */ /* customize things here */
--header-primary: {{colors.on_surface.default.hex}} !important; :root {
--header-secondary: {{colors.on_surface_variant.default.hex}} !important; /* font, change to 'gg sans' for default discord font*/
--text-normal: {{colors.on_surface.default.hex}} !important; --font: 'figtree';
--text-default: {{colors.on_surface.default.hex}};
--text-muted: {{colors.on_surface_variant.default.hex}} !important;
--text-primary: {{colors.on_surface.default.hex}};
--text-secondary: {{colors.on_surface_variant.default.hex}};
--text-tertiary: {{colors.on_surface_variant.default.hex}} !important;
--interactive-normal: {{colors.on_surface.default.hex}} !important;
--interactive-muted: {{colors.on_surface_variant.default.hex}};
--interactive-hover: {{colors.on_surface.default.hex}};
--interactive-active: {{colors.on_surface.default.hex}};
/* Main Background Colors - Bar color (mSurface) colors.surface.default.hex*/ /* top left corner text */
--background-primary: {{colors.surface_variant.default.hex}} !important; --corner-text: 'Midnight';
--background-floating: {{colors.surface_variant.default.hex}} !important;
--background-surface-high: {{colors.surface_variant.default.hex}} !important;
--modal-background: {{colors.surface_variant.default.hex}} !important;
--app-background-frame: {{colors.surface_variant.default.hex}} !important;
--home-background: {{colors.surface_variant.default.hex}} !important;
--chat-background: {{colors.surface_variant.default.hex}} !important;
--chat-background-default: {{colors.surface_variant.default.hex}} !important;
--chat-input-container-background: {{colors.surface_container.default.hex}} !important;
/* Secondary Background Colors - Workspace color (mSurfaceVariant) */
--background-secondary: {{colors.surface.default.hex}} !important;
--background-secondary-alt: {{colors.surface.default.hex}} !important;
--background-surface-higher: {{colors.surface.default.hex}} !important;
--background-base-low: {{colors.surface.default.hex}} !important;
--background-base-lower: {{colors.surface.default.hex}} !important;
--channeltextarea-background: {{colors.surface_container.default.hex}} !important;
--modal-footer-background: {{colors.surface.default.hex}} !important;
/* New Messages Banner */
--background-mentioned: {{colors.primary.default.hex}}15 !important;
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
--text-mentioned: {{colors.on_surface.default.hex}} !important;
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
--text-mentioned-link: {{colors.primary.default.hex}} !important;
/* Additional Discord-specific variables for new messages banner */
--background-message-automod: {{colors.primary.default.hex}}15 !important;
--background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
--background-message-highlight: {{colors.primary.default.hex}}15 !important;
--background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
/* Discord unread messages banner specific variables */
--background-mentioned: {{colors.primary.default.hex}}15 !important;
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
--text-mentioned: {{colors.on_surface.default.hex}} !important;
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
--text-mentioned-link: {{colors.primary.default.hex}} !important;
/* Additional Discord banner text variables */
--text-normal: {{colors.on_surface.default.hex}} !important;
--text-default: {{colors.on_surface.default.hex}} !important;
--text-primary: {{colors.on_surface.default.hex}} !important;
--text-secondary: {{colors.on_surface_variant.default.hex}} !important;
--text-tertiary: {{colors.on_surface_variant.default.hex}} !important;
--text-muted: {{colors.on_surface_variant.default.hex}} !important;
--interactive-normal: {{colors.on_surface.default.hex}} !important;
--interactive-muted: {{colors.on_surface_variant.default.hex}} !important;
/* Additional Discord banner variables */
--background-message-automod: {{colors.primary.default.hex}}15 !important;
--background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
--background-message-highlight: {{colors.primary.default.hex}}15 !important;
--background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
--background-message-hover: {{colors.surface_variant.default.hex}}50 !important;
--background-modifier-hover: {{colors.surface_variant.default.hex}}80 !important;
--background-modifier-selected: {{colors.primary.default.hex}}20 !important;
--background-modifier-accent: {{colors.primary.default.hex}}30 !important;
--background-modifier-active: {{colors.primary.default.hex}}25 !important;
/* Chat Input Improvements */
--text-input-background: {{colors.surface_container.default.hex}} !important;
--text-input-border: {{colors.outline.default.hex}} !important;
--text-input-border-hover: {{colors.primary.default.hex}} !important;
/* Additional Discord-specific input variables */
--deprecated-text-input-bg: {{colors.surface_container.default.hex}} !important;
--deprecated-text-input-border: {{colors.outline.default.hex}} !important;
--deprecated-text-input-border-hover: {{colors.primary.default.hex}} !important;
--input-background: {{colors.surface_container.default.hex}} !important;
--input-border: {{colors.outline.default.hex}} !important;
--input-placeholder-text: {{colors.on_surface_variant.default.hex}} !important;
/* Elevated/Container Backgrounds */
--background-tertiary: {{colors.surface_container.default.hex}} !important;
--background-accent: {{colors.surface_container.default.hex}} !important;
--background-surface-highest: {{colors.surface_container_high.default.hex}} !important;
--background-base-lowest: {{colors.surface_container.default.hex}} !important;
/* Border Colors */ /* color of status indicators and window controls */
--border-faint: {{colors.outline_variant.default.hex}}; --online-indicator: {{colors.inverse_primary.default.hex}}; /* change to #23a55a for default green */
--border-strong: {{colors.surface_container.default.hex}}; --dnd-indicator: {{colors.error.default.hex}}; /* change to #f13f43 for default red */
--border-normal: {{colors.surface_container_high.default.hex}}; --idle-indicator: {{colors.tertiary_container.default.hex}}; /* change to #f0b232 for default yellow */
--border-subtle: {{colors.surface.default.hex}} !important; --streaming-indicator: {{colors.on_primary.default.hex}}; /* change to #593695 for default purple */
--chat-border: {{colors.surface_container_high.default.hex}};
/* Status Colors */ /* accent colors */
--status-positive: {{colors.tertiary.default.hex}}; --accent-1: {{colors.tertiary.default.hex}}; /* links */
--status-positive-background: {{colors.tertiary.default.hex}}; --accent-2: {{colors.primary.default.hex}}; /* general unread/mention elements, some icons when active */
--status-positive-text: {{colors.on_tertiary.default.hex}}; --accent-3: {{colors.primary.default.hex}}; /* accent buttons */
--text-positive: {{colors.tertiary.default.hex}}; --accent-4: {{colors.surface_bright.default.hex}}; /* accent buttons when hovered */
--text-feedback-positive: {{colors.tertiary.default.hex}}; --accent-5: {{colors.primary_fixed_dim.default.hex}}; /* accent buttons when clicked */
--background-feedback-positive: {{colors.tertiary.default.hex}}20; --mention: {{colors.surface.default.hex}}; /* mentions & mention messages */
--info-positive-background: {{colors.tertiary.default.hex}}20; --mention-hover: {{colors.surface_bright.default.hex}}; /* mentions & mention messages when hovered */
--info-positive-foreground: {{colors.tertiary.default.hex}};
--info-positive-text: {{colors.on_surface.default.hex}};
--status-warning: {{colors.secondary.default.hex}}; /* text colors */
--status-warning-background: {{colors.secondary.default.hex}}; --text-0: {{colors.surface.default.hex}}; /* text on colored elements */
--status-warning-text: {{colors.on_secondary.default.hex}}; --text-1: {{colors.on_surface.default.hex}}; /* other normally white text */
--text-warning: {{colors.secondary.default.hex}}; --text-2: {{colors.on_surface.default.hex}}; /* headings and important text */
--text-feedback-warning: {{colors.secondary.default.hex}}; --text-3: {{colors.on_surface_variant.default.hex}}; /* normal text */
--background-feedback-warning: {{colors.secondary.default.hex}}20; --text-4: {{colors.on_surface_variant.default.hex}}; /* icon buttons and channels */
--info-warning-background: {{colors.secondary.default.hex}}20; --text-5: {{colors.outline.default.hex}}; /* muted channels/chats and timestamps */
--info-warning-foreground: {{colors.secondary.default.hex}};
--info-warning-text: {{colors.on_surface.default.hex}};
--status-danger: {{colors.error.default.hex}}; /* background and dark colors */
--status-danger-background: {{colors.error.default.hex}}; --bg-1: {{colors.primary.default.hex}}; /* dark buttons when clicked */
--status-danger-text: {{colors.on_error.default.hex}}; --bg-2: {{colors.surface_container_high.default.hex}}; /* dark buttons */
--text-danger: {{colors.error.default.hex}}; --bg-3: {{colors.surface_container_low.default.hex}}; /* spacing, secondary elements */
--text-feedback-critical: {{colors.error.default.hex}}; --bg-4: {{colors.surface.default.hex}}; /* main background color */
--background-feedback-critical: {{colors.error.default.hex}}20; --hover: {{colors.surface_bright.default.hex}}; /* channels and buttons when hovered */
--info-danger-background: {{colors.error.default.hex}}20; --active: {{colors.surface_bright.default.hex}}; /* channels and buttons when clicked or selected */
--info-danger-foreground: {{colors.error.default.hex}}; --message-hover: {{colors.surface_bright.default.hex}}; /* messages when hovered */
--info-danger-text: {{colors.on_surface.default.hex}};
/* Button Colors */ /* amount of spacing and padding */
--button-secondary-background: {{colors.surface_variant.default.hex}} !important; --spacing: 12px;
--button-secondary-background-hover: {{colors.surface_container.default.hex}};
--button-secondary-background-active: {{colors.surface_container.default.hex}};
--button-secondary-background-disabled: {{colors.surface_variant.default.hex}};
--button-secondary-text: {{colors.on_surface.default.hex}} !important;
--button-filled-brand-text: {{colors.on_primary.default.hex}}; /* animations */
--button-filled-brand-background: {{colors.primary.default.hex}}; /* ALL ANIMATIONS CAN BE DISABLED WITH REDUCED MOTION IN DISCORD SETTINGS */
--button-filled-brand-background-hover: {{colors.primary.default.hex}}; --list-item-transition: 0.2s ease; /* channels/members/settings hover transition */
--button-filled-brand-background-active: {{colors.primary.default.hex}}; --unread-bar-transition: 0.2s ease; /* unread bar moving into view transition */
--moon-spin-transition: 0.4s ease; /* moon icon spin */
--icon-spin-transition: 1s ease; /* round icon button spin (settings, emoji, etc.) */
/* Input Colors */ /* corner roundness (border-radius) */
--input-background: {{colors.surface_container.default.hex}}; --roundness-xl: 22px; /* roundness of big panel outer corners */
--input-border: {{colors.outline.default.hex}}; --roundness-l: 20px; /* popout panels */
--input-placeholder-text: {{colors.on_surface_variant.default.hex}}; --roundness-m: 16px; /* smaller panels, images, embeds */
--roundness-s: 12px; /* members, settings inputs */
--roundness-xs: 10px; /* channels, buttons */
--roundness-xxs: 8px; /* searchbar, small elements */
/* Scrollbar Colors */ /* direct messages moon icon */
--scrollbar-thin-thumb: {{colors.primary.default.hex}}; /* change to block to show, none to hide */
--scrollbar-thin-track: transparent; --discord-icon: none; /* discord icon */
--scrollbar-auto-thumb: {{colors.primary.default.hex}}; --moon-icon: block; /* moon icon */
--scrollbar-auto-track: {{colors.surface_container_high.default.hex}}; --moon-icon-url: url('https://upload.wikimedia.org/wikipedia/commons/c/c4/Font_Awesome_5_solid_moon.svg'); /* custom icon url */
--scrollbar-auto-scrollbar-color-thumb: {{colors.primary.default.hex}}; --moon-icon-size: auto;
--scrollbar-auto-scrollbar-color-track: {{colors.surface_container_high.default.hex}};
/* Icon Colors */ /* filter uncolorable elements to fit theme */
--icon-muted: {{colors.on_surface_variant.default.hex}}; /* (just set to none, they're too much work to configure) */
--icon-default: {{colors.on_surface.default.hex}}; --login-bg-filter: saturate(0.3) hue-rotate(-15deg) brightness(0.4); /* login background artwork */
--icon-primary: {{colors.on_surface.default.hex}}; --green-to-accent-3-filter: hue-rotate(56deg) saturate(1.43); /* add friend page explore icon */
--icon-secondary: {{colors.on_surface_variant.default.hex}}; --blurple-to-accent-3-filter: hue-rotate(304deg) saturate(0.84) brightness(1.2); /* add friend page school icon */
--icon-tertiary: {{colors.on_surface_variant.default.hex}} !important;
/* Channel Colors */
--channels-default: {{colors.on_surface_variant.default.hex}} !important;
--channel-icon: {{colors.on_surface_variant.default.hex}} !important;
--channel-text-area-placeholder: {{colors.on_surface.default.hex}}80;
/* Selection and Hover States */
--background-modifier-hover: {{colors.surface_variant.default.hex}}80;
--background-modifier-selected: {{colors.primary.default.hex}}20 !important;
--background-modifier-accent: {{colors.primary.default.hex}}30;
--background-modifier-active: {{colors.primary.default.hex}}25 !important;
--background-message-hover: {{colors.surface_variant.default.hex}}50 !important;
--background-message-highlight: {{colors.primary.default.hex}}15;
--background-message-highlight-hover: {{colors.primary.default.hex}}20;
/* Code Block - Use workspace background */
--background-code: {{colors.surface_container.default.hex}};
--textbox-markdown-syntax: {{colors.on_surface_variant.default.hex}};
/* Spoiler */
--spoiler-revealed-background: {{colors.surface_container.default.hex}};
--spoiler-hidden-background: {{colors.surface_variant.default.hex}};
/* White/Black Overrides */
--white: {{colors.on_surface.default.hex}};
--white-400: {{colors.on_surface.default.hex}};
--white-500: {{colors.on_surface.default.hex}};
--white-600: {{colors.on_surface_variant.default.hex}};
--white-700: {{colors.on_surface_variant.default.hex}};
--black-500: {{colors.surface_container_high.default.hex}};
/* Force styling for Discord unread messages banner */
--unread-bar-background: {{colors.primary.default.hex}}15 !important;
--unread-bar-text: {{colors.on_surface.default.hex}} !important;
--unread-bar-hover: {{colors.primary.default.hex}}20 !important;
/* Additional Discord unread bar variables */
--background-mentioned: {{colors.primary.default.hex}}15 !important;
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
--text-mentioned: {{colors.on_surface.default.hex}} !important;
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
--text-mentioned-link: {{colors.primary.default.hex}} !important;
/* Discord banner specific variables */
--background-message-automod: {{colors.primary.default.hex}}15 !important;
--background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
--background-message-highlight: {{colors.primary.default.hex}}15 !important;
--background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
/* Discord unread bar specific variables */
--background-mentioned: {{colors.primary.default.hex}}15 !important;
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
--text-mentioned: {{colors.on_surface.default.hex}} !important;
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
--text-mentioned-link: {{colors.primary.default.hex}} !important;
/* Additional Discord text variables that might affect the banner */
--text-normal: {{colors.on_surface.default.hex}} !important;
--text-default: {{colors.on_surface.default.hex}} !important;
--text-primary: {{colors.on_surface.default.hex}} !important;
--text-secondary: {{colors.on_surface_variant.default.hex}} !important;
--text-tertiary: {{colors.on_surface_variant.default.hex}} !important;
--text-muted: {{colors.on_surface_variant.default.hex}} !important;
--interactive-normal: {{colors.on_surface.default.hex}} !important;
--interactive-muted: {{colors.on_surface_variant.default.hex}} !important;
/* Force styling for Discord chat input */
--chat-input-background: {{colors.surface_container.default.hex}} !important;
--chat-input-placeholder: {{colors.on_surface_variant.default.hex}} !important;
/* Discord unread messages banner specific variables */
--new-messages-bar-background: {{colors.surface_container.default.hex}} !important;
--new-messages-bar-text: {{colors.on_surface.default.hex}} !important;
--new-messages-bar-hover: {{colors.surface_container_high.default.hex}} !important;
--bar-button-background: {{colors.surface_container.default.hex}} !important;
--bar-button-text: {{colors.on_surface.default.hex}} !important;
--bar-button-hover: {{colors.surface_container_high.default.hex}} !important;
} }
.visual-refresh.theme-dark ::selection, /* Selected chat/friend text */
.visual-refresh .theme-dark ::selection { .selected_f5eb4b,
background-color: {{colors.primary.default.hex}}; .selected_f6f816 .link_d8bfb3 {
color: var(--text-0) !important;
background: var(--accent-3) !important;
} }
/* Force Discord unread messages banner styling */ .selected_f6f816 .link_d8bfb3 * {
.visual-refresh.theme-dark .newMessagesBar__0f481, color: var(--text-0) !important;
.visual-refresh.theme-dark .barButtonMain__0f481, fill: var(--text-0) !important;
.visual-refresh.theme-dark .barButtonBase__0f481,
.visual-refresh.theme-dark .span__0f481 {
background-color: {{colors.surface_container.default.hex}} !important;
color: {{colors.on_surface.default.hex}} !important;
} }
.visual-refresh.theme-dark .newMessagesBar__0f481:hover, /* Make channel name text less visible (darker) */
.visual-refresh.theme-dark .barButtonMain__0f481:hover, .name__2ea32 {
.visual-refresh.theme-dark .barButtonBase__0f481:hover { color: var(--text-5) !important;
background-color: {{colors.surface_container_high.default.hex}} !important; opacity: 0.7 !important;
} }
/* Force Discord chat input styling */ /* Make unread channel names brighter */
.visual-refresh.theme-dark .channelTextArea-rNsIhG, .link__2ea32[aria-label*="unread"] .name__2ea32 {
.visual-refresh.theme-dark .channelTextArea-rNsIhG *, color: var(--text-2) !important;
.visual-refresh.theme-dark .scrollableContainer-2NUZem, opacity: 1 !important;
.visual-refresh.theme-dark [data-slate-editor="true"] { font-weight: 600 !important;
background-color: {{colors.surface_container.default.hex}} !important;
} }
.visual-refresh.theme-dark [data-slate-editor="true"]::placeholder,
.visual-refresh.theme-dark .channelTextArea-rNsIhG [data-slate-editor="true"]::placeholder {
color: {{colors.on_surface_variant.default.hex}} !important;
}
/* Discord Emoji Picker Theming */
.visual-refresh.theme-dark .contentWrapper__08434,
.visual-refresh.theme-dark .emojiPicker_c0e32c,
.visual-refresh.theme-dark .wrapper_c0e32c {
background-color: {{colors.surface.default.hex}} !important;
}
.visual-refresh.theme-dark .nav__08434,
.visual-refresh.theme-dark .navList__08434 {
background-color: {{colors.surface.default.hex}} !important;
}
.visual-refresh.theme-dark .navButton__08434 {
background-color: {{colors.surface.default.hex}} !important;
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .navButtonActive__08434 {
background-color: {{colors.surface.default.hex}} !important;
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .searchBar_c0e32c,
.visual-refresh.theme-dark .input_a45028 {
background-color: {{colors.surface.default.hex}} !important;
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .input_a45028::placeholder {
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .header_c656ac,
.visual-refresh.theme-dark .header__14245,
.visual-refresh.theme-dark .wrapper__14245 {
background-color: {{colors.surface_variant.default.hex}} !important;
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .headerLabel__14245 {
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .interactive__14245 {
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .header__14245 {
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .header__14245 * {
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .headerIcon__14245 svg,
.visual-refresh.theme-dark .headerCollapseIcon__14245 svg {
color: {{colors.on_surface.default.hex}} !important;
fill: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .emojiItem_fc7141 {
background-color: transparent !important;
}
.visual-refresh.theme-dark .emojiItem_fc7141:hover {
background-color: {{colors.surface_container.default.hex}} !important;
}
.visual-refresh.theme-dark .emojiItemSelected_fc7141 {
background-color: {{colors.primary.default.hex}}20 !important;
}
.visual-refresh.theme-dark .inspector_aeaaeb {
background-color: {{colors.surface_container.default.hex}} !important;
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .categoryList_c0e32c {
background-color: {{colors.surface.default.hex}} !important;
}
.visual-refresh.theme-dark .categoryItem_b9ee0c {
background-color: transparent !important;
}
.visual-refresh.theme-dark .categoryItem_b9ee0c:hover {
background-color: {{colors.surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .categoryItemDefaultCategorySelected_b9ee0c {
background-color: {{colors.surface_variant.default.hex}} !important;
}
/* Additional Discord emoji picker elements */
.visual-refresh.theme-dark .navItem__08434 {
background-color: {{colors.surface_variant.default.hex}} !important;
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .navItem__08434:hover {
background-color: {{colors.surface_container.default.hex}} !important;
}
.visual-refresh.theme-dark .stickersNavItem__08434 {
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .wrapper__14245 {
background-color: {{colors.surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .headerLabel__14245 {
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .headerIcon__14245 svg,
.visual-refresh.theme-dark .headerCollapseIcon__14245 svg {
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .interactive__14245:hover {
background-color: {{colors.surface_container.default.hex}} !important;
}
/* Chat input styling */
.visual-refresh.theme-dark .scrollableContainer__74017,
.visual-refresh.theme-dark .themedBackground__74017,
.visual-refresh.theme-dark .inner__74017,
.visual-refresh.theme-dark .textArea__74017,
.visual-refresh.theme-dark .slateContainer_ec4baf,
.visual-refresh.theme-dark .markup__75297,
.visual-refresh.theme-dark .editor__1b31f,
.visual-refresh.theme-dark .slateTextArea_ec4baf {
background-color: {{colors.surface_container.default.hex}} !important;
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .emptyText__1464f {
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .placeholder__1b31f {
color: {{colors.on_surface_variant.default.hex}} !important;
}
/* Message content styling */
.visual-refresh.theme-dark .messageContent_c19a55 {
color: {{colors.on_surface.default.hex}} !important;
background-color: {{colors.surface.default.hex}} !important;
}
.visual-refresh.theme-dark .messageContent_c19a55 .markup__75297 {
color: {{colors.on_surface.default.hex}} !important;
background-color: {{colors.surface.default.hex}} !important;
}
/* Message background styling */
.visual-refresh.theme-dark .message__5126c,
.visual-refresh.theme-dark .cozyMessage__5126c,
.visual-refresh.theme-dark .wrapper_c19a55,
.visual-refresh.theme-dark .contents_c19a55 {
background-color: {{colors.surface.default.hex}} !important;
}
/* Message hover effects */
.visual-refresh.theme-dark .message__5126c:hover {
background-color: {{colors.surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .message__5126c:hover * {
color: {{colors.on_surface.default.hex}} !important;
}
/* Remove Discord's native quote/reply bar */
.visual-refresh.theme-dark .message__5126c::before {
display: none !important;
}
.visual-refresh.theme-dark .message__5126c.hasReply_c19a55::before {
display: none !important;
}
/* Channel styling - darker text for read channels */
.visual-refresh.theme-dark .link__2ea32 .name__2ea32 {
color: {{colors.outline.default.hex}} !important;
}
/* Unread channels keep normal color */
.visual-refresh.theme-dark .link__2ea32[aria-label*="unread"] .name__2ea32 {
color: {{colors.on_surface.default.hex}} !important;
}
/* Search input styling */
.visual-refresh.theme-dark .inner_a45028 {
background-color: {{colors.surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .input_a45028 {
background-color: transparent !important;
}
.visual-refresh.theme-dark .input_a45028::placeholder {
color: {{colors.on_surface_variant.default.hex}} !important;
}
/* Chat input placeholder styling */
.visual-refresh.theme-dark .emptyText__1464f {
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .slateTextArea_ec4baf > div:first-child .emptyText__1464f::before {
content: "send a message" !important;
color: {{colors.on_surface_variant.default.hex}} !important;
}
/* Hide placeholder when input is focused */
.visual-refresh.theme-dark .slateTextArea_ec4baf:focus .emptyText__1464f::before,
.visual-refresh.theme-dark .markup__75297:focus .emptyText__1464f::before {
display: none !important;
}
.visual-refresh.theme-dark .message__5126c:hover .messageContent_c19a55,
.visual-refresh.theme-dark .message__5126c:hover .markup__75297,
.visual-refresh.theme-dark .message__5126c:hover .header_c19a55,
.visual-refresh.theme-dark .message__5126c:hover .headerText_c19a55,
.visual-refresh.theme-dark .message__5126c:hover .username_c19a55,
.visual-refresh.theme-dark .message__5126c:hover .timestamp_c19a55 {
background-color: {{colors.surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .categoryIcon_b9ee0c svg {
color: {{colors.on_surface_variant.default.hex}} !important;
}
.visual-refresh.theme-dark .unicodeShortcut_b9ee0c {
background-color: {{colors.surface_container.default.hex}} !important;
color: {{colors.on_surface.default.hex}} !important;
}
.visual-refresh.theme-dark .unicodeShortcut_b9ee0c:hover {
background-color: {{colors.surface_container_high.default.hex}} !important;
}
.visual-refresh.theme-dark .unicodeShortcut_b9ee0c svg {
color: {{colors.on_surface.default.hex}} !important;
}
/* Number badge styling */
.visual-refresh.theme-dark .numberBadge__2b1f5 {
color: {{colors.surface.default.hex}} !important;
background-color: {{colors.primary.default.hex}} !important;
}
/* New badge styling */
.visual-refresh.theme-dark .newBadge__4ed1a {
color: {{colors.surface.default.hex}} !important;
background-color: {{colors.primary.default.hex}} !important;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -4,4 +4,4 @@
# Can be installed from AUR "qmlfmt-git" # Can be installed from AUR "qmlfmt-git"
# Requires qt6-5compat # Requires qt6-5compat
find . -name "*.qml" -print -exec qmlfmt -e -b 120 -t 2 -i 2 -w {} \; find . -name "*.qml" -print -exec qmlfmt -e -b 360 -t 2 -i 2 -w {} \;

View file

@ -34,8 +34,7 @@ Singleton {
try { try {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId) if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return iconFromName(fallback, fallback) return iconFromName(fallback, fallback)
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup( const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
appId) : DesktopEntries.byId(appId)
const name = entry && entry.icon ? entry.icon : "" const name = entry && entry.icon ? entry.icon : ""
return iconFromName(name || fallback, fallback) return iconFromName(name || fallback, fallback)
} catch (e) { } catch (e) {

View file

@ -14,7 +14,7 @@ Singleton {
readonly property string defaultIcon: TablerIcons.defaultIcon readonly property string defaultIcon: TablerIcons.defaultIcon
readonly property var icons: TablerIcons.icons readonly property var icons: TablerIcons.icons
readonly property var aliases: TablerIcons.aliases readonly property var aliases: TablerIcons.aliases
readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.woff2" readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.ttf"
Component.onCompleted: { Component.onCompleted: {
Logger.log("Icons", "Service started") Logger.log("Icons", "Service started")

View file

@ -20,7 +20,8 @@ Singleton {
"folder-open": "folder-open", "folder-open": "folder-open",
"download": "download", "download": "download",
"toast-notice": "circle-check", "toast-notice": "circle-check",
"toast-warning": "exclamation-circle", "toast-warning": "alert-circle",
"toast-error": "circle-x",
"question-mark": "question-mark", "question-mark": "question-mark",
"search": "search", "search": "search",
"warning": "exclamation-circle", "warning": "exclamation-circle",
@ -39,21 +40,20 @@ Singleton {
"balanced": "scale", "balanced": "scale",
"powersaver": "leaf", "powersaver": "leaf",
"storage": "database", "storage": "database",
"ethernet": "sitemap-filled", "ethernet": "sitemap",
"keyboard": "keyboard", "keyboard": "keyboard",
"shutdown": "power", "shutdown": "power",
"lock": "lock-filled", "lock": "lock",
"logout": "logout", "logout": "logout",
"reboot": "refresh", "reboot": "refresh",
"suspend": "player-pause-filled", "suspend": "player-pause",
"nightlight-on": "moon-filled", "nightlight-on": "moon",
"nightlight-off": "moon-off", "nightlight-off": "moon-off",
"nightlight-forced": "moon-stars", "nightlight-forced": "moon-stars",
"bell": "bell", "bell": "bell",
"bell-off": "bell-off", "bell-off": "bell-off",
"keep-awake-on": "mug", "keep-awake-on": "mug",
"keep-awake-off": "mug-off", "keep-awake-off": "mug-off",
"panel": "clipboard-filled",
"disc": "disc-filled", "disc": "disc-filled",
"image": "photo", "image": "photo",
"dark-mode": "contrast-filled", "dark-mode": "contrast-filled",
@ -84,41 +84,47 @@ Singleton {
"volume-zero": "volume-3", "volume-zero": "volume-3",
"volume-low": "volume-2", "volume-low": "volume-2",
"volume-high": "volume", "volume-high": "volume",
"weather-sun": "sun-filled", "weather-sun": "sun",
"weather-cloud-sun": "sun-wind",
"weather-cloud": "cloud", "weather-cloud": "cloud",
"weather-cloud-haze": "cloud-fog", "weather-cloud-haze": "cloud-fog",
"weather-cloud-lightning": "cloud-bolt",
"weather-cloud-rain": "cloud-rain", "weather-cloud-rain": "cloud-rain",
"weather-cloud-snow": "cloud-snow", "weather-cloud-snow": "cloud-snow",
"weather-cloud-lightning": "cloud-bolt", "weather-cloud-sun": "cloud-sun",
"brightness-low": "brightness-down-filled", "brightness-low": "brightness-down-filled",
"brightness-high": "brightness-up-filled", "brightness-high": "brightness-up-filled",
"settings-general": "adjustments-horizontal", "settings-general": "adjustments-horizontal",
"settings-bar": "capsule-horizontal", "settings-bar": "capsule-horizontal",
"settings-dock": "layout-bottombar",
"settings-launcher": "rocket", "settings-launcher": "rocket",
"settings-audio": "device-speaker", "settings-audio": "device-speaker",
"settings-display": "device-desktop", "settings-display": "device-desktop",
"settings-network": "sitemap-filled", "settings-network": "sitemap",
"settings-brightness": "brightness-up-filled", "settings-brightness": "brightness-up",
"settings-weather": "cloud-rain", "settings-weather": "cloud-sun",
"settings-color-scheme": "palette", "settings-color-scheme": "palette",
"settings-wallpaper": "paint", "settings-wallpaper": "paint",
"settings-wallpaper-selector": "library-photo", "settings-wallpaper-selector": "library-photo",
"settings-screen-recorder": "video", "settings-screen-recorder": "video",
"settings-hooks": "link", "settings-hooks": "link",
"settings-notification": "bell",
"settings-about": "info-square-rounded", "settings-about": "info-square-rounded",
"bluetooth": "bluetooth", "bluetooth": "bluetooth",
"bt-device-generic": "bluetooth", "bt-device-generic": "bluetooth",
"bt-device-headphones": "headphones-filled", "bt-device-headphones": "headphones",
"bt-device-mouse": "mouse-2", "bt-device-mouse": "mouse-2",
"bt-device-keyboard": "bluetooth", "bt-device-keyboard": "bluetooth",
"bt-device-phone": "device-mobile-filled", "bt-device-phone": "device-mobile",
"bt-device-watch": "device-watch", "bt-device-watch": "device-watch",
"bt-device-speaker": "device-speaker", "bt-device-speaker": "device-speaker",
"bt-device-tv": "device-tv" "bt-device-tv": "device-tv",
"noctalia": "noctalia"
} }
// Fonts Codepoints - do not change // Fonts Codepoints - do not change!
// Some icons have been disabled because Qt's text rendering engine recognizes
// some ranges as special Unicode characters at a very low level.
// ex: fe00-fe2f
readonly property var icons: { readonly property var icons: {
"123": "\u{f554}", "123": "\u{f554}",
"360": "\u{f62f}", "360": "\u{f62f}",
@ -255,8 +261,8 @@ Singleton {
"align-left": "\u{ea09}", "align-left": "\u{ea09}",
"align-left-2": "\u{ff00}", "align-left-2": "\u{ff00}",
"align-right": "\u{ea0a}", "align-right": "\u{ea0a}",
"align-right-2": "\u{feff}", "alpha"//"align-right-2": "\u{feff}",
"alpha": "\u{f543}", : "\u{f543}",
"alphabet-arabic": "\u{ff2f}", "alphabet-arabic": "\u{ff2f}",
"alphabet-bangla": "\u{ff2e}", "alphabet-bangla": "\u{ff2e}",
"alphabet-cyrillic": "\u{f1df}", "alphabet-cyrillic": "\u{f1df}",
@ -2044,6 +2050,7 @@ Singleton {
"cloud-snow": "\u{ea73}", "cloud-snow": "\u{ea73}",
"cloud-star": "\u{f85b}", "cloud-star": "\u{f85b}",
"cloud-storm": "\u{ea74}", "cloud-storm": "\u{ea74}",
"cloud-sun": "\u{ea7a}",
"cloud-up": "\u{f85c}", "cloud-up": "\u{f85c}",
"cloud-upload": "\u{ea75}", "cloud-upload": "\u{ea75}",
"cloud-x": "\u{f85d}", "cloud-x": "\u{f85d}",
@ -3087,8 +3094,8 @@ Singleton {
"friends": "\u{eab0}", "friends": "\u{eab0}",
"friends-off": "\u{f136}", "friends-off": "\u{f136}",
"frustum": "\u{fa9f}", "frustum": "\u{fa9f}",
"frustum-off": "\u{fa9d}", "frustum-plus"//"frustum-off": "\u{fa9d}",
"frustum-plus": "\u{fa9e}", : "\u{fa9e}",
"function": "\u{f225}", "function": "\u{f225}",
"function-filled": "\u{fc2b}", "function-filled": "\u{fc2b}",
"function-off": "\u{f3f0}", "function-off": "\u{f3f0}",
@ -3347,13 +3354,13 @@ Singleton {
"hexagon-letter-x": "\u{f479}", "hexagon-letter-x": "\u{f479}",
"hexagon-letter-x-filled": "\u{fe30}", "hexagon-letter-x-filled": "\u{fe30}",
"hexagon-letter-y": "\u{f47a}", "hexagon-letter-y": "\u{f47a}",
"hexagon-letter-y-filled": "\u{fe2f}", "hexagon-letter-z"//"hexagon-letter-y-filled": "\u{fe2f}",
"hexagon-letter-z": "\u{f47b}", : "\u{f47b}",
"hexagon-letter-z-filled": "\u{fe2e}", "hexagon-minus"//"hexagon-letter-z-filled": "\u{fe2e}",
"hexagon-minus": "\u{fc8f}", : "\u{fc8f}",
"hexagon-minus-2": "\u{fc8e}", "hexagon-minus-2": "\u{fc8e}",
"hexagon-minus-filled": "\u{fe2d}", "hexagon-number-0"//"hexagon-minus-filled": "\u{fe2d}",
"hexagon-number-0": "\u{f459}", : "\u{f459}",
"hexagon-number-0-filled": "\u{f74c}", "hexagon-number-0-filled": "\u{f74c}",
"hexagon-number-1": "\u{f45a}", "hexagon-number-1": "\u{f45a}",
"hexagon-number-1-filled": "\u{f74d}", "hexagon-number-1-filled": "\u{f74d}",
@ -3376,8 +3383,8 @@ Singleton {
"hexagon-off": "\u{ee9c}", "hexagon-off": "\u{ee9c}",
"hexagon-plus": "\u{fc45}", "hexagon-plus": "\u{fc45}",
"hexagon-plus-2": "\u{fc90}", "hexagon-plus-2": "\u{fc90}",
"hexagon-plus-filled": "\u{fe2c}", "hexagonal-prism"//"hexagon-plus-filled": "\u{fe2c}",
"hexagonal-prism": "\u{faa5}", : "\u{faa5}",
"hexagonal-prism-off": "\u{faa3}", "hexagonal-prism-off": "\u{faa3}",
"hexagonal-prism-plus": "\u{faa4}", "hexagonal-prism-plus": "\u{faa4}",
"hexagonal-pyramid": "\u{faa8}", "hexagonal-pyramid": "\u{faa8}",
@ -3407,8 +3414,8 @@ Singleton {
"home-eco": "\u{f351}", "home-eco": "\u{f351}",
"home-edit": "\u{f352}", "home-edit": "\u{f352}",
"home-exclamation": "\u{f33c}", "home-exclamation": "\u{f33c}",
"home-filled": "\u{fe2b}", "home-hand"//"home-filled": "\u{fe2b}",
"home-hand": "\u{f504}", : "\u{f504}",
"home-heart": "\u{f353}", "home-heart": "\u{f353}",
"home-infinity": "\u{f505}", "home-infinity": "\u{f505}",
"home-link": "\u{f354}", "home-link": "\u{f354}",
@ -3525,8 +3532,8 @@ Singleton {
"ironing-2-filled": "\u{1006e}", "ironing-2-filled": "\u{1006e}",
"ironing-3": "\u{f2f6}", "ironing-3": "\u{f2f6}",
"ironing-3-filled": "\u{1006d}", "ironing-3-filled": "\u{1006d}",
"ironing-filled": "\u{fe2a}", "ironing-off"//"ironing-filled": "\u{fe2a}",
"ironing-off": "\u{f2f7}", : "\u{f2f7}",
"ironing-steam": "\u{f2f9}", "ironing-steam": "\u{f2f9}",
"ironing-steam-filled": "\u{1006c}", "ironing-steam-filled": "\u{1006c}",
"ironing-steam-off": "\u{f2f8}", "ironing-steam-off": "\u{f2f8}",
@ -3536,8 +3543,8 @@ Singleton {
"italic": "\u{eb93}", "italic": "\u{eb93}",
"jacket": "\u{f661}", "jacket": "\u{f661}",
"jetpack": "\u{f581}", "jetpack": "\u{f581}",
"jetpack-filled": "\u{fe29}", "jewish-star"//"jetpack-filled": "\u{fe29}",
"jewish-star": "\u{f3ff}", : "\u{f3ff}",
"jewish-star-filled": "\u{f67e}", "jewish-star-filled": "\u{f67e}",
"join-bevel": "\u{ff4c}", "join-bevel": "\u{ff4c}",
"join-round": "\u{ff4b}", "join-round": "\u{ff4b}",
@ -3551,8 +3558,8 @@ Singleton {
"kering": "\u{efb8}", "kering": "\u{efb8}",
"kerning": "\u{efb8}", "kerning": "\u{efb8}",
"key": "\u{eac7}", "key": "\u{eac7}",
"key-filled": "\u{fe28}", "key-off"//"key-filled": "\u{fe28}",
"key-off": "\u{f14b}", : "\u{f14b}",
"keyboard": "\u{ebd6}", "keyboard": "\u{ebd6}",
"keyboard-filled": "\u{100a2}", "keyboard-filled": "\u{100a2}",
"keyboard-hide": "\u{ec7e}", "keyboard-hide": "\u{ec7e}",
@ -3608,20 +3615,20 @@ Singleton {
"layers-union": "\u{eacb}", "layers-union": "\u{eacb}",
"layout": "\u{eadb}", "layout": "\u{eadb}",
"layout-2": "\u{eacc}", "layout-2": "\u{eacc}",
"layout-2-filled": "\u{fe27}", "layout-align-left"//"layout-2-filled": "\u{fe27}",
"layout-align-bottom": "\u{eacd}", // "layout-align-bottom": "\u{eacd}",
"layout-align-bottom-filled": "\u{fe26}", //"layout-align-bottom-filled": "\u{fe26}",
"layout-align-center": "\u{eace}", // "layout-align-center": "\u{eace}",
"layout-align-center-filled": "\u{fe25}", //"layout-align-center-filled": "\u{fe25}",
"layout-align-left": "\u{eacf}", : "\u{eacf}",
"layout-align-left-filled": "\u{fe24}", "layout-align-middle"// "layout-align-left-filled": "\u{fe24}",
"layout-align-middle": "\u{ead0}", : "\u{ead0}",
"layout-align-middle-filled": "\u{fe23}", "layout-align-right"//"layout-align-middle-filled": "\u{fe23}",
"layout-align-right": "\u{ead1}", : "\u{ead1}",
"layout-align-right-filled": "\u{fe22}", "layout-align-top"//"layout-align-right-filled": "\u{fe22}",
"layout-align-top": "\u{ead2}", : "\u{ead2}",
"layout-align-top-filled": "\u{fe21}", "layout-board"//"layout-align-top-filled": "\u{fe21}",
"layout-board": "\u{ef95}", : "\u{ef95}",
"layout-board-filled": "\u{10182}", "layout-board-filled": "\u{10182}",
"layout-board-split": "\u{ef94}", "layout-board-split": "\u{ef94}",
"layout-board-split-filled": "\u{10183}", "layout-board-split-filled": "\u{10183}",
@ -3633,8 +3640,8 @@ Singleton {
"layout-bottombar-filled": "\u{fc37}", "layout-bottombar-filled": "\u{fc37}",
"layout-bottombar-inactive": "\u{fd45}", "layout-bottombar-inactive": "\u{fd45}",
"layout-cards": "\u{ec13}", "layout-cards": "\u{ec13}",
"layout-cards-filled": "\u{fe20}", "layout-collage"// "layout-cards-filled": "\u{fe20}",
"layout-collage": "\u{f389}", : "\u{f389}",
"layout-columns": "\u{ead4}", "layout-columns": "\u{ead4}",
"layout-dashboard": "\u{f02c}", "layout-dashboard": "\u{f02c}",
"layout-dashboard-filled": "\u{fe1f}", "layout-dashboard-filled": "\u{fe1f}",
@ -4115,14 +4122,14 @@ Singleton {
"microphone": "\u{eaf0}", "microphone": "\u{eaf0}",
"microphone-2": "\u{ef2c}", "microphone-2": "\u{ef2c}",
"microphone-2-off": "\u{f40d}", "microphone-2-off": "\u{f40d}",
"microphone-filled": "\u{fe0f}", "microphone-off"//"microphone-filled": "\u{fe0f}",
"microphone-off": "\u{ed16}", : "\u{ed16}",
"microscope": "\u{ef64}", "microscope": "\u{ef64}",
"microscope-filled": "\u{10166}", "microscope-filled": "\u{10166}",
"microscope-off": "\u{f40e}", "microscope-off": "\u{f40e}",
"microwave": "\u{f248}", "microwave": "\u{f248}",
"microwave-filled": "\u{fe0e}", "microwave-off"//"microwave-filled": "\u{fe0e}",
"microwave-off": "\u{f264}", : "\u{f264}",
"military-award": "\u{f079}", "military-award": "\u{f079}",
"military-rank": "\u{efcf}", "military-rank": "\u{efcf}",
"military-rank-filled": "\u{ff5e}", "military-rank-filled": "\u{ff5e}",
@ -4295,6 +4302,7 @@ Singleton {
"news-off": "\u{f167}", "news-off": "\u{f167}",
"nfc": "\u{eeb7}", "nfc": "\u{eeb7}",
"nfc-off": "\u{f168}", "nfc-off": "\u{f168}",
"noctalia": "\u{ec33}",
"no-copyright": "\u{efb9}", "no-copyright": "\u{efb9}",
"no-creative-commons": "\u{efba}", "no-creative-commons": "\u{efba}",
"no-derivatives": "\u{efbb}", "no-derivatives": "\u{efbb}",
@ -4354,18 +4362,18 @@ Singleton {
"number-4-small": "\u{fcf9}", "number-4-small": "\u{fcf9}",
"number-40-small": "\u{fffa}", "number-40-small": "\u{fffa}",
"number-41-small": "\u{fff9}", "number-41-small": "\u{fff9}",
"number-42-small": "\u{fff8}", "number-5"//"number-42-small": "\u{fff8}",
"number-43-small": "\u{fff7}", // "number-43-small": "\u{fff7}",
"number-44-small": "\u{fff6}", // "number-44-small": "\u{fff6}",
"number-45-small": "\u{fff5}", // "number-45-small": "\u{fff5}",
"number-46-small": "\u{fff4}", // "number-46-small": "\u{fff4}",
"number-47-small": "\u{fff3}", // "number-47-small": "\u{fff3}",
"number-48-small": "\u{fff2}", // "number-48-small": "\u{fff2}",
"number-49-small": "\u{fff1}", // "number-49-small": "\u{fff1}",
"number-5": "\u{edf5}", : "\u{edf5}",
"number-5-small": "\u{fcfa}", "number-5-small": "\u{fcfa}",
"number-50-small": "\u{fff0}", "number-51-small"// "number-50-small": "\u{fff0}",
"number-51-small": "\u{ffef}", : "\u{ffef}",
"number-52-small": "\u{ffee}", "number-52-small": "\u{ffee}",
"number-53-small": "\u{ffed}", "number-53-small": "\u{ffed}",
"number-54-small": "\u{ffec}", "number-54-small": "\u{ffec}",
@ -4805,11 +4813,11 @@ Singleton {
"quote": "\u{efbe}", "quote": "\u{efbe}",
"quote-filled": "\u{1009c}", "quote-filled": "\u{1009c}",
"quote-off": "\u{f188}", "quote-off": "\u{f188}",
"quotes": "\u{fb1e}", "radar"//"quotes": "\u{fb1e}",
"radar": "\u{f017}", : "\u{f017}",
"radar-2": "\u{f016}", "radar-2": "\u{f016}",
"radar-filled": "\u{fe0d}", "radar-off"//"radar-filled": "\u{fe0d}",
"radar-off": "\u{f41f}", : "\u{f41f}",
"radio": "\u{ef2d}", "radio": "\u{ef2d}",
"radio-off": "\u{f420}", "radio-off": "\u{f420}",
"radioactive": "\u{ecc0}", "radioactive": "\u{ecc0}",
@ -4869,12 +4877,12 @@ Singleton {
"regex-off": "\u{f421}", "regex-off": "\u{f421}",
"registered": "\u{eb14}", "registered": "\u{eb14}",
"relation-many-to-many": "\u{ed7f}", "relation-many-to-many": "\u{ed7f}",
"relation-many-to-many-filled": "\u{fe0c}", "relation-one-to-many"//"relation-many-to-many-filled": "\u{fe0c}",
"relation-one-to-many": "\u{ed80}", : "\u{ed80}",
"relation-one-to-many-filled": "\u{fe0b}", "relation-one-to-one"//"relation-one-to-many-filled": "\u{fe0b}",
"relation-one-to-one": "\u{ed81}", : "\u{ed81}",
"relation-one-to-one-filled": "\u{fe0a}", "reload"//"relation-one-to-one-filled": "\u{fe0a}",
"reload": "\u{f3ae}", : "\u{f3ae}",
"reorder": "\u{fc15}", "reorder": "\u{fc15}",
"repeat": "\u{eb72}", "repeat": "\u{eb72}",
"repeat-off": "\u{f18e}", "repeat-off": "\u{f18e}",
@ -5026,8 +5034,8 @@ Singleton {
"search": "\u{eb1c}", "search": "\u{eb1c}",
"search-off": "\u{f19c}", "search-off": "\u{f19c}",
"section": "\u{eed5}", "section": "\u{eed5}",
"section-filled": "\u{fe09}", "section-sign"//"section-filled": "\u{fe09}",
"section-sign": "\u{f019}", : "\u{f019}",
"seeding": "\u{ed51}", "seeding": "\u{ed51}",
"seeding-filled": "\u{10006}", "seeding-filled": "\u{10006}",
"seeding-off": "\u{f19d}", "seeding-off": "\u{f19d}",
@ -5235,8 +5243,8 @@ Singleton {
"sort-z-a": "\u{f550}", "sort-z-a": "\u{f550}",
"sos": "\u{f24a}", "sos": "\u{f24a}",
"soup": "\u{ef2e}", "soup": "\u{ef2e}",
"soup-filled": "\u{fe08}", "soup-off"//"soup-filled": "\u{fe08}",
"soup-off": "\u{f42d}", : "\u{f42d}",
"source-code": "\u{f4a2}", "source-code": "\u{f4a2}",
"space": "\u{ec0c}", "space": "\u{ec0c}",
"space-off": "\u{f1aa}", "space-off": "\u{f1aa}",
@ -5329,22 +5337,22 @@ Singleton {
"square-half": "\u{effb}", "square-half": "\u{effb}",
"square-key": "\u{f638}", "square-key": "\u{f638}",
"square-letter-a": "\u{f47c}", "square-letter-a": "\u{f47c}",
"square-letter-a-filled": "\u{fe07}", "square-letter-b"//"square-letter-a-filled": "\u{fe07}",
"square-letter-b": "\u{f47d}", : "\u{f47d}",
"square-letter-b-filled": "\u{fe06}", "square-letter-c"//"square-letter-b-filled": "\u{fe06}",
"square-letter-c": "\u{f47e}", : "\u{f47e}",
"square-letter-c-filled": "\u{fe05}", "square-letter-d"//"square-letter-c-filled": "\u{fe05}",
"square-letter-d": "\u{f47f}", : "\u{f47f}",
"square-letter-d-filled": "\u{fe04}", "square-letter-e"//"square-letter-d-filled": "\u{fe04}",
"square-letter-e": "\u{f480}", : "\u{f480}",
"square-letter-e-filled": "\u{fe03}", "square-letter-f"//"square-letter-e-filled": "\u{fe03}",
"square-letter-f": "\u{f481}", : "\u{f481}",
"square-letter-f-filled": "\u{fe02}", "square-letter-g"//"square-letter-f-filled": "\u{fe02}",
"square-letter-g": "\u{f482}", : "\u{f482}",
"square-letter-g-filled": "\u{fe01}", "square-letter-h"//"square-letter-g-filled": "\u{fe01}",
"square-letter-h": "\u{f483}", : "\u{f483}",
"square-letter-h-filled": "\u{fe00}", "square-letter-i"//"square-letter-h-filled": "\u{fe00}",
"square-letter-i": "\u{f484}", : "\u{f484}",
"square-letter-i-filled": "\u{fdff}", "square-letter-i-filled": "\u{fdff}",
"square-letter-j": "\u{f485}", "square-letter-j": "\u{f485}",
"square-letter-j-filled": "\u{fdfe}", "square-letter-j-filled": "\u{fdfe}",

205
Commons/KeyboardLayout.qml Normal file
View file

@ -0,0 +1,205 @@
pragma Singleton
import QtQuick
QtObject {
id: root
// Comprehensive language name to ISO code mapping
property var languageMap: {
"english"// English variants
: "us",
"american": "us",
"united states": "us",
"us english": "us",
"british": "gb",
"uk": "ua",
"united kingdom"// FIXED: Ukrainian language code should map to Ukraine
: "gb",
"english (uk)": "gb",
"canadian": "ca",
"canada": "ca",
"canadian english": "ca",
"australian": "au",
"australia": "au",
"swedish"// Nordic countries
: "se",
"svenska": "se",
"sweden": "se",
"norwegian": "no",
"norsk": "no",
"norway": "no",
"danish": "dk",
"dansk": "dk",
"denmark": "dk",
"finnish": "fi",
"suomi": "fi",
"finland": "fi",
"icelandic": "is",
"íslenska": "is",
"iceland": "is",
"german"// Western/Central European Germanic
: "de",
"deutsch": "de",
"germany": "de",
"austrian": "at",
"austria": "at",
"österreich": "at",
"swiss": "ch",
"switzerland": "ch",
"schweiz": "ch",
"suisse": "ch",
"dutch": "nl",
"nederlands": "nl",
"netherlands": "nl",
"holland": "nl",
"belgian": "be",
"belgium": "be",
"belgië": "be",
"belgique": "be",
"french"// Romance languages (Western/Southern Europe)
: "fr",
"français": "fr",
"france": "fr",
"canadian french": "ca",
"spanish": "es",
"español": "es",
"spain": "es",
"castilian": "es",
"italian": "it",
"italiano": "it",
"italy": "it",
"portuguese": "pt",
"português": "pt",
"portugal": "pt",
"catalan": "ad",
"català": "ad",
"andorra": "ad",
"romanian"// Eastern European Romance
: "ro",
"română": "ro",
"romania": "ro",
"russian"// Slavic languages (Eastern Europe)
: "ru",
"русский": "ru",
"russia": "ru",
"polish": "pl",
"polski": "pl",
"poland": "pl",
"czech": "cz",
"čeština": "cz",
"czech republic": "cz",
"slovak": "sk",
"slovenčina": "sk",
"slovakia": "sk",
"uk": "ua",
"ukrainian"// Ukrainian language code
: "ua",
"українська": "ua",
"ukraine": "ua",
"bulgarian": "bg",
"български": "bg",
"bulgaria": "bg",
"serbian": "rs",
"srpski": "rs",
"serbia": "rs",
"croatian": "hr",
"hrvatski": "hr",
"croatia": "hr",
"slovenian": "si",
"slovenščina": "si",
"slovenia": "si",
"bosnian": "ba",
"bosanski": "ba",
"bosnia": "ba",
"macedonian": "mk",
"македонски": "mk",
"macedonia": "mk",
"irish"// Celtic languages (Western Europe)
: "ie",
"gaeilge": "ie",
"ireland": "ie",
"welsh": "gb",
"cymraeg": "gb",
"wales": "gb",
"scottish": "gb",
"gàidhlig": "gb",
"scotland": "gb",
"estonian"// Baltic languages (Northern Europe)
: "ee",
"eesti": "ee",
"estonia": "ee",
"latvian": "lv",
"latviešu": "lv",
"latvia": "lv",
"lithuanian": "lt",
"lietuvių": "lt",
"lithuania": "lt",
"hungarian"// Other European languages
: "hu",
"magyar": "hu",
"hungary": "hu",
"greek": "gr",
"ελληνικά": "gr",
"greece": "gr",
"albanian": "al",
"shqip": "al",
"albania": "al",
"maltese": "mt",
"malti": "mt",
"malta": "mt",
"turkish"// West/Southwest Asian languages
: "tr",
"türkçe": "tr",
"turkey": "tr",
"arabic": "ar",
"العربية": "ar",
"arab": "ar",
"hebrew": "il",
"עברית": "il",
"israel": "il",
"brazilian"// South American languages
: "br",
"brazilian portuguese": "br",
"brasil": "br",
"brazil": "br",
"japanese"// East Asian languages
: "jp",
"日本語": "jp",
"japan": "jp",
"korean": "kr",
"한국어": "kr",
"korea": "kr",
"south korea": "kr",
"chinese": "cn",
"中文": "cn",
"china": "cn",
"simplified chinese": "cn",
"traditional chinese": "tw",
"taiwan": "tw",
"繁體中文": "tw",
"thai"// Southeast Asian languages
: "th",
"ไทย": "th",
"thailand": "th",
"vietnamese": "vn",
"tiếng việt": "vn",
"vietnam": "vn",
"hindi"// South Asian languages
: "in",
"हिन्दी": "in",
"india": "in",
"afrikaans"// African languages
: "za",
"south africa": "za",
"south african": "za",
"qwerty"// Layout variants
: "us",
"dvorak": "us",
"colemak": "us",
"workman": "us",
"azerty": "fr",
"norman": "fr",
"qwertz": "de"
}
}

View file

@ -13,11 +13,8 @@ Singleton {
// Default config directory: ~/.config/noctalia // Default config directory: ~/.config/noctalia
// Default cache directory: ~/.cache/noctalia // Default cache directory: ~/.cache/noctalia
property string shellName: "noctalia" property string shellName: "noctalia"
property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
|| Quickshell.env( property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/"
"HOME") + "/.config") + "/" + shellName + "/"
property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env(
"HOME") + "/.cache") + "/" + shellName + "/"
property string cacheDirImages: cacheDir + "images/" property string cacheDirImages: cacheDir + "images/"
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json") property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
@ -58,8 +55,7 @@ Singleton {
} }
} }
if (!hasValidBarMonitor) { if (!hasValidBarMonitor) {
Logger.warn("Settings", Logger.warn("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens")
"No configured bar monitors found on system, clearing bar monitor list to show on all screens")
adapter.bar.monitors = [] adapter.bar.monitors = []
} else { } else {
@ -138,13 +134,18 @@ Singleton {
widget.showIcon = widget.showIcon !== undefined ? widget.showIcon : adapter.bar.showActiveWindowIcon widget.showIcon = widget.showIcon !== undefined ? widget.showIcon : adapter.bar.showActiveWindowIcon
break break
case "Battery": case "Battery":
widget.alwaysShowPercentage = widget.alwaysShowPercentage widget.alwaysShowPercentage = widget.alwaysShowPercentage !== undefined ? widget.alwaysShowPercentage : adapter.bar.alwaysShowBatteryPercentage
!== undefined ? widget.alwaysShowPercentage : adapter.bar.alwaysShowBatteryPercentage
break break
case "Clock": case "Clock":
widget.showDate = widget.showDate !== undefined ? widget.showDate : adapter.location.showDateWithClock
widget.use12HourClock = widget.use12HourClock !== undefined ? widget.use12HourClock : adapter.location.use12HourClock widget.use12HourClock = widget.use12HourClock !== undefined ? widget.use12HourClock : adapter.location.use12HourClock
widget.reverseDayMonth = widget.reverseDayMonth !== undefined ? widget.reverseDayMonth : adapter.location.reverseDayMonth widget.reverseDayMonth = widget.reverseDayMonth !== undefined ? widget.reverseDayMonth : adapter.location.reverseDayMonth
if (widget.showDate !== undefined) {
widget.displayFormat = "time-date"
} else if (widget.showSeconds) {
widget.displayFormat = "time-seconds"
}
delete widget.showDate
delete widget.showSeconds
break break
case "MediaMini": case "MediaMini":
widget.showAlbumArt = widget.showAlbumArt !== undefined ? widget.showAlbumArt : adapter.audio.showMiniplayerAlbumArt widget.showAlbumArt = widget.showAlbumArt !== undefined ? widget.showAlbumArt : adapter.audio.showMiniplayerAlbumArt
@ -174,7 +175,7 @@ Singleton {
} }
} }
// Backup the widget definition before altering // Compare settings, to detect if something has been upgraded
const widgetAfter = JSON.stringify(widget) const widgetAfter = JSON.stringify(widget)
return (widgetAfter !== widgetBefore) return (widgetAfter !== widgetBefore)
} }
@ -258,14 +259,19 @@ Singleton {
JsonAdapter { JsonAdapter {
id: adapter id: adapter
property int settingsVersion: 1 property int settingsVersion: 2
// bar // bar
property JsonObject bar: JsonObject { property JsonObject bar: JsonObject {
property string position: "top" // "top" or "bottom" property string position: "top" // "top", "bottom", "left", or "right"
property real backgroundOpacity: 1.0 property real backgroundOpacity: 1.0
property list<string> monitors: [] property list<string> monitors: []
// Floating bar settings
property bool floating: false
property real marginVertical: 0.25
property real marginHorizontal: 0.25
property bool showActiveWindowIcon: true // TODO: delete property bool showActiveWindowIcon: true // TODO: delete
property bool alwaysShowBatteryPercentage: false // TODO: delete property bool alwaysShowBatteryPercentage: false // TODO: delete
property bool showNetworkStats: false // TODO: delete property bool showNetworkStats: false // TODO: delete
@ -316,7 +322,9 @@ Singleton {
property string avatarImage: defaultAvatar property string avatarImage: defaultAvatar
property bool dimDesktop: false property bool dimDesktop: false
property bool showScreenCorners: false property bool showScreenCorners: false
property bool forceBlackScreenCorners: false
property real radiusRatio: 1.0 property real radiusRatio: 1.0
property real screenRadiusRatio: 1.0
// Animation speed multiplier (0.1x - 2.0x) // Animation speed multiplier (0.1x - 2.0x)
property real animationSpeed: 1.0 property real animationSpeed: 1.0
} }
@ -376,6 +384,7 @@ Singleton {
property bool autoHide: false property bool autoHide: false
property bool exclusive: false property bool exclusive: false
property real backgroundOpacity: 1.0 property real backgroundOpacity: 1.0
property real floatingRatio: 1.0
property list<string> monitors: [] property list<string> monitors: []
} }
@ -391,6 +400,10 @@ Singleton {
property list<string> monitors: [] property list<string> monitors: []
// Last time the user opened the notification history (ms since epoch) // Last time the user opened the notification history (ms since epoch)
property real lastSeenTs: 0 property real lastSeenTs: 0
// Duration settings for different urgency levels (in seconds)
property int lowUrgencyDuration: 3
property int normalUrgencyDuration: 8
property int criticalUrgencyDuration: 15
} }
// audio // audio
@ -437,6 +450,7 @@ Singleton {
property bool foot: false property bool foot: false
property bool fuzzel: false property bool fuzzel: false
property bool vesktop: false property bool vesktop: false
property bool pywalfox: false
property bool enableUserTemplates: false property bool enableUserTemplates: false
} }

View file

@ -35,6 +35,9 @@ Singleton {
property int radiusM: 16 * Settings.data.general.radiusRatio property int radiusM: 16 * Settings.data.general.radiusRatio
property int radiusL: 20 * Settings.data.general.radiusRatio property int radiusL: 20 * Settings.data.general.radiusRatio
//screen Radii
property int screenRadius: 20 * Settings.data.general.screenRadiusRatio
// Border // Border
property int borderS: 3 property int borderS: 3
property int borderM: 3 property int borderM: 3
@ -63,9 +66,9 @@ Singleton {
property int animationSlowest: Math.round(750 / Settings.data.general.animationSpeed) property int animationSlowest: Math.round(750 / Settings.data.general.animationSpeed)
// Dimensions // Dimensions
property int barHeight: 36 property int barHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
property int capsuleHeight: (barHeight * 0.73) property int capsuleHeight: (barHeight * 0.73)
property int baseWidgetSize: 32 property int baseWidgetSize: (barHeight * 0.9)
property int sliderWidth: 200 property int sliderWidth: 200
// Delays // Delays

View file

@ -43,9 +43,7 @@ Variants {
// Fillmode default is "crop" // Fillmode default is "crop"
property real fillMode: 1.0 property real fillMode: 1.0
property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0)
Settings.data.wallpaper.fillColor.g,
Settings.data.wallpaper.fillColor.b, 1.0)
// On startup assign wallpaper immediately // On startup assign wallpaper immediately
Component.onCompleted: { Component.onCompleted: {
@ -229,8 +227,7 @@ Variants {
from: 0.0 from: 0.0
to: 1.0 to: 1.0
// The stripes shader feels faster visually, we make it a bit slower here. // The stripes shader feels faster visually, we make it a bit slower here.
duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration * 1.6 : Settings.data.wallpaper.transitionDuration
* 1.6 : Settings.data.wallpaper.transitionDuration
easing.type: Easing.InOutCubic easing.type: Easing.InOutCubic
onFinished: { onFinished: {
// Swap images after transition completes // Swap images after transition completes

View file

@ -60,6 +60,7 @@ Variants {
MultiEffect { MultiEffect {
anchors.fill: parent anchors.fill: parent
source: bgImage source: bgImage
autoPaddingEnabled: false
blurEnabled: true blurEnabled: true
blur: 0.48 blur: 0.48
blurMax: 128 blurMax: 128
@ -68,9 +69,7 @@ Variants {
// Make the overview darker // Make the overview darker
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface, color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface, Style.opacityMedium) : Qt.alpha(Color.mOnSurface, Style.opacityMedium)
Style.opacityMedium) : Qt.alpha(Color.mOnSurface,
Style.opacityMedium)
} }
} }
} }

View file

@ -19,9 +19,9 @@ Loader {
property real scaling: ScalingService.getScreenScale(screen) property real scaling: ScalingService.getScreenScale(screen)
screen: modelData screen: modelData
property color cornerColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
property real cornerRadius: 20 * scaling property real cornerRadius: Style.screenRadius * scaling
property real cornerSize: 20 * scaling property real cornerSize: Style.screenRadius * scaling
Connections { Connections {
target: ScalingService target: ScalingService
@ -46,12 +46,12 @@ Loader {
} }
margins { margins {
top: ((modelData && Settings.data.bar.monitors.includes(modelData.name)) // When bar is floating, corners should be at screen edges (no margins)
|| (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "top" // When bar is not floating, respect bar margins as before
&& Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0 top: !Settings.data.bar.floating && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "top" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
bottom: ((modelData && Settings.data.bar.monitors.includes(modelData.name)) bottom: !Settings.data.bar.floating && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "bottom" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
|| (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "bottom" left: !Settings.data.bar.floating && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "left" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
&& Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0 right: !Settings.data.bar.floating && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "right" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
} }
mask: Region {} mask: Region {}

View file

@ -27,118 +27,222 @@ Variants {
} }
} }
active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false
|| (Settings.data.bar.monitors.length === 0)) : false
sourceComponent: PanelWindow { sourceComponent: PanelWindow {
screen: modelData || null screen: modelData || null
WlrLayershell.namespace: "noctalia-bar" WlrLayershell.namespace: "noctalia-bar"
implicitHeight: Math.round(Style.barHeight * scaling) implicitHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? screen.height : Math.round(Style.barHeight * scaling)
implicitWidth: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? Math.round(Style.barHeight * scaling) : screen.width
color: Color.transparent color: Color.transparent
anchors { anchors {
top: Settings.data.bar.position === "top" top: Settings.data.bar.position === "top" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
bottom: Settings.data.bar.position === "bottom" bottom: Settings.data.bar.position === "bottom" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
left: true left: Settings.data.bar.position === "left" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
right: true right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
}
// Floating bar margins - only apply when floating is enabled
margins {
top: Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
bottom: Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
left: Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
right: Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
} }
Item { Item {
anchors.fill: parent anchors.fill: parent
clip: true clip: true
// Background fill // Background fill with shadow
Rectangle { Rectangle {
id: bar id: bar
anchors.fill: parent anchors.fill: parent
color: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) color: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
// Floating bar rounded corners
radius: Settings.data.bar.floating ? Style.radiusL : 0
} }
// ------------------------------ // For vertical bars, use a single column layout
// Left Section - Dynamic Widgets Loader {
Row { id: verticalBarLayout
id: leftSection anchors.fill: parent
objectName: "leftSection" visible: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
sourceComponent: verticalBarComponent
}
height: parent.height // For horizontal bars, use the original three-section layout
anchors.left: parent.left Loader {
anchors.leftMargin: Style.marginS * scaling id: horizontalBarLayout
anchors.verticalCenter: parent.verticalCenter anchors.fill: parent
spacing: Style.marginS * scaling visible: Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
sourceComponent: horizontalBarComponent
}
Repeater { // Main layout components
model: Settings.data.bar.widgets.left Component {
delegate: NWidgetLoader { id: verticalBarComponent
widgetId: (modelData.id !== undefined ? modelData.id : "") Item {
widgetProps: { anchors.fill: parent
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen), // Top section (left widgets)
"widgetId": modelData.id, ColumnLayout {
"barSection": parent.objectName, anchors.horizontalCenter: parent.horizontalCenter
"sectionWidgetIndex": index, anchors.top: parent.top
"sectionWidgetsCount": Settings.data.bar.widgets.left.length anchors.topMargin: Style.marginM * root.scaling
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.left
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "left",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
}
Layout.alignment: Qt.AlignHCenter
}
} }
}
// Center section (center widgets)
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.center
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "center",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
}
Layout.alignment: Qt.AlignHCenter
}
}
}
// Bottom section (right widgets)
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.marginM * root.scaling
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.right
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "right",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
}
Layout.alignment: Qt.AlignHCenter
}
}
} }
} }
} }
// ------------------------------ Component {
// Center Section - Dynamic Widgets id: horizontalBarComponent
Row { Item {
id: centerSection anchors.fill: parent
objectName: "centerSection"
height: parent.height // Left Section
anchors.horizontalCenter: parent.horizontalCenter RowLayout {
anchors.verticalCenter: parent.verticalCenter id: leftSection
spacing: Style.marginS * scaling objectName: "leftSection"
anchors.left: parent.left
Repeater { anchors.leftMargin: Style.marginS * root.scaling
model: Settings.data.bar.widgets.center
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"barSection": parent.objectName,
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
}
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.left
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "left",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
}
Layout.alignment: Qt.AlignVCenter
}
}
} }
}
}
// ------------------------------ // Center Section
// Right Section - Dynamic Widgets RowLayout {
Row { id: centerSection
id: rightSection objectName: "centerSection"
objectName: "rightSection" anchors.horizontalCenter: parent.horizontalCenter
height: parent.height
anchors.right: bar.right
anchors.rightMargin: Style.marginS * scaling
anchors.verticalCenter: bar.verticalCenter
spacing: Style.marginS * scaling
Repeater {
model: Settings.data.bar.widgets.right
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"barSection": parent.objectName,
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
}
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.center
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "center",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
}
Layout.alignment: Qt.AlignVCenter
}
}
}
// Right Section
RowLayout {
id: rightSection
objectName: "rightSection"
anchors.right: parent.right
anchors.rightMargin: Style.marginS * root.scaling
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.right
delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "right",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
}
Layout.alignment: Qt.AlignVCenter
}
}
} }
} }
} }

View file

@ -31,8 +31,7 @@ PopupWindow {
implicitWidth: menuWidth * scaling implicitWidth: menuWidth * scaling
// Use the content height of the Flickable for implicit height // Use the content height of the Flickable for implicit height
implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2 * scaling))
flickable.contentHeight + (Style.marginS * 2 * scaling))
visible: false visible: false
color: Color.transparent color: Color.transparent
anchor.item: anchorItem anchor.item: anchorItem
@ -159,8 +158,7 @@ PopupWindow {
NText { NText {
id: text id: text
Layout.fillWidth: true Layout.fillWidth: true
color: (modelData?.enabled color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..." text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..."
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -180,7 +178,7 @@ PopupWindow {
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
visible: modelData?.hasChildren ?? false visible: modelData?.hasChildren ?? false
color: Color.mOnSurface color: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface)
} }
} }
@ -222,9 +220,32 @@ PopupWindow {
const submenuWidth = menuWidth * scaling // Assuming a similar width as the parent const submenuWidth = menuWidth * scaling // Assuming a similar width as the parent
const overlap = 4 * scaling // A small overlap to bridge the mouse path const overlap = 4 * scaling // A small overlap to bridge the mouse path
// Check if there's enough space on the right // Determine submenu opening direction based on bar position and available space
let openLeft = false
// Check bar position first
const barPosition = Settings.data.bar.position
const globalPos = entry.mapToGlobal(0, 0) const globalPos = entry.mapToGlobal(0, 0)
const openLeft = (globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width))
if (barPosition === "right") {
// Bar is on the right, prefer opening submenus to the left
openLeft = true
} else if (barPosition === "left") {
// Bar is on the left, prefer opening submenus to the right
openLeft = false
} else {
// Bar is horizontal (top/bottom) or undefined, use space-based logic
openLeft = (globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width))
// Secondary check: ensure we don't open off-screen
if (openLeft && globalPos.x - submenuWidth < 0) {
// Would open off the left edge, force right opening
openLeft = false
} else if (!openLeft && globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width)) {
// Would open off the right edge, force left opening
openLeft = true
}
}
// Position with overlap // Position with overlap
const anchorX = openLeft ? -submenuWidth + overlap : entry.width - overlap const anchorX = openLeft ? -submenuWidth + overlap : entry.width - overlap

View file

@ -8,20 +8,19 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
RowLayout { Item {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -37,34 +36,85 @@ RowLayout {
readonly property real minWidth: Math.max(1, screen.width * 0.06) readonly property real minWidth: Math.max(1, screen.width * 0.06)
readonly property real maxWidth: minWidth * 2 readonly property real maxWidth: minWidth * 2
readonly property string barPosition: Settings.data.bar.position
implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
function getTitle() { function getTitle() {
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : "" try {
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
} catch (e) {
Logger.warn("ActiveWindow", "Error getting title:", e)
return ""
}
} }
Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling
visible: getTitle() !== "" visible: getTitle() !== ""
function calculatedVerticalHeight() {
// Use standard widget height like other widgets
return Math.round(Style.capsuleHeight * scaling)
}
function calculatedHorizontalWidth() {
let total = Style.marginM * 2 * scaling // internal padding
if (showIcon) {
total += Style.baseWidgetSize * 0.5 * scaling + 2 * scaling // icon + spacing
}
// Calculate actual text width more accurately
const title = getTitle()
if (title !== "") {
// Estimate text width: average character width * number of characters
const avgCharWidth = Style.fontSizeS * scaling * 0.6 // rough estimate
const titleWidth = Math.min(title.length * avgCharWidth, 80 * scaling)
total += titleWidth
}
// Row layout handles spacing between widgets
return Math.max(total, Style.capsuleHeight * scaling) // Minimum width
}
function getAppIcon() { function getAppIcon() {
// Try CompositorService first try {
const focusedWindow = CompositorService.getFocusedWindow() // Try CompositorService first
if (focusedWindow && focusedWindow.appId) { const focusedWindow = CompositorService.getFocusedWindow()
const idValue = focusedWindow.appId if (focusedWindow && focusedWindow.appId) {
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue) try {
return AppIcons.iconForAppId(normalizedId.toLowerCase()) const idValue = focusedWindow.appId
} const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
const iconResult = AppIcons.iconForAppId(normalizedId.toLowerCase())
// Fallback to ToplevelManager if (iconResult && iconResult !== "") {
if (ToplevelManager && ToplevelManager.activeToplevel) { return iconResult
const activeToplevel = ToplevelManager.activeToplevel }
if (activeToplevel.appId) { } catch (iconError) {
const idValue2 = activeToplevel.appId Logger.warn("ActiveWindow", "Error getting icon from CompositorService:", iconError)
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2) }
return AppIcons.iconForAppId(normalizedId2.toLowerCase())
} }
}
return "" // Fallback to ToplevelManager
if (ToplevelManager && ToplevelManager.activeToplevel) {
try {
const activeToplevel = ToplevelManager.activeToplevel
if (activeToplevel.appId) {
const idValue2 = activeToplevel.appId
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
const iconResult2 = AppIcons.iconForAppId(normalizedId2.toLowerCase())
if (iconResult2 && iconResult2 !== "") {
return iconResult2
}
}
} catch (fallbackError) {
Logger.warn("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError)
}
}
return ""
} catch (e) {
Logger.warn("ActiveWindow", "Error in getAppIcon:", e)
return ""
}
} }
// A hidden text element to safely measure the full title width // A hidden text element to safely measure the full title width
@ -79,27 +129,31 @@ RowLayout {
Rectangle { Rectangle {
id: windowTitleRect id: windowTitleRect
visible: root.visible visible: root.visible
Layout.preferredWidth: contentLayout.implicitWidth + Style.marginM * 2 * scaling anchors.left: parent.left
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) anchors.verticalCenter: parent.verticalCenter
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling) radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
Item { Item {
id: mainContainer id: mainContainer
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
clip: true clip: true
// Horizontal layout for top/bottom bars
RowLayout { RowLayout {
id: contentLayout id: horizontalLayout
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginS * scaling spacing: 2 * scaling
visible: barPosition === "top" || barPosition === "bottom"
// Window icon // Window icon
Item { Item {
Layout.preferredWidth: Style.fontSizeL * scaling * 1.2 Layout.preferredWidth: Style.baseWidgetSize * 0.5 * scaling
Layout.preferredHeight: Style.fontSizeL * scaling * 1.2 Layout.preferredHeight: Style.baseWidgetSize * 0.5 * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: getTitle() !== "" && showIcon visible: getTitle() !== "" && showIcon
@ -110,16 +164,28 @@ RowLayout {
asynchronous: true asynchronous: true
smooth: true smooth: true
visible: source !== "" visible: source !== ""
// Handle loading errors gracefully
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("ActiveWindow", "Failed to load icon:", source)
}
}
} }
} }
NText { NText {
id: titleText id: titleText
Layout.preferredWidth: { Layout.preferredWidth: {
if (mouseArea.containsMouse) { try {
return Math.round(Math.min(fullTitleMetrics.contentWidth, root.maxWidth * scaling)) if (mouseArea.containsMouse) {
} else { return Math.round(Math.min(fullTitleMetrics.contentWidth, root.maxWidth * scaling))
return Math.round(Math.min(fullTitleMetrics.contentWidth, root.minWidth * scaling)) } else {
return Math.round(Math.min(fullTitleMetrics.contentWidth, 80 * scaling)) // Limited width for horizontal bars
}
} catch (e) {
Logger.warn("ActiveWindow", "Error calculating width:", e)
return 80 * scaling
} }
} }
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@ -141,12 +207,65 @@ RowLayout {
} }
} }
// Vertical layout for left/right bars - icon only
Item {
id: verticalLayout
anchors.centerIn: parent
width: parent.width - Style.marginXS * scaling * 2
height: parent.height - Style.marginXS * scaling * 2
visible: barPosition === "left" || barPosition === "right"
// Window icon
Item {
width: Style.baseWidgetSize * 0.5 * scaling
height: Style.baseWidgetSize * 0.5 * scaling
anchors.centerIn: parent
visible: getTitle() !== "" && showIcon
IconImage {
id: windowIconVertical
anchors.fill: parent
source: getAppIcon()
asynchronous: true
smooth: true
visible: source !== ""
// Handle loading errors gracefully
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("ActiveWindow", "Failed to load icon:", source)
}
}
}
}
}
// Mouse area for hover detection // Mouse area for hover detection
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onEntered: {
if (barPosition === "left" || barPosition === "right") {
tooltip.show()
}
}
onExited: {
if (barPosition === "left" || barPosition === "right") {
tooltip.hide()
}
}
}
// Hover tooltip with full title (only for vertical bars)
NTooltip {
id: tooltip
target: verticalLayout
text: getTitle()
positionLeft: barPosition === "right"
positionRight: barPosition === "left"
delay: 500
} }
} }
} }
@ -154,10 +273,20 @@ RowLayout {
Connections { Connections {
target: CompositorService target: CompositorService
function onActiveWindowChanged() { function onActiveWindowChanged() {
windowIcon.source = Qt.binding(getAppIcon) try {
windowIcon.source = Qt.binding(getAppIcon)
windowIconVertical.source = Qt.binding(getAppIcon)
} catch (e) {
Logger.warn("ActiveWindow", "Error in onActiveWindowChanged:", e)
}
} }
function onWindowListChanged() { function onWindowListChanged() {
windowIcon.source = Qt.binding(getAppIcon) try {
windowIcon.source = Qt.binding(getAppIcon)
windowIconVertical.source = Qt.binding(getAppIcon)
} catch (e) {
Logger.warn("ActiveWindow", "Error in onWindowListChanged:", e)
}
} }
} }
} }

View file

@ -14,13 +14,12 @@ Item {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -30,21 +29,18 @@ Item {
return {} return {}
} }
// Resolve settings: try user settings or defaults from BarWidgetRegistry readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
readonly property bool alwaysShowPercentage: widgetSettings.alwaysShowPercentage readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
!== undefined ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
readonly property real warningThreshold: widgetSettings.warningThreshold
!== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
// Test mode // Test mode
readonly property bool testMode: false readonly property bool testMode: false
readonly property int testPercent: 90 readonly property int testPercent: 100
readonly property bool testCharging: false readonly property bool testCharging: false
// Main properties // Main properties
readonly property var battery: UPower.displayDevice readonly property var battery: UPower.displayDevice
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
&& battery.isPresent)
readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0) readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false) readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
property bool hasNotifiedLowBattery: false property bool hasNotifiedLowBattery: false
@ -89,11 +85,12 @@ Item {
id: pill id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady)
charging, isReady) text: (isReady || testMode) ? Math.round(percent) : "-"
text: (isReady || testMode) ? Math.round(percent) + "%" : "-" suffix: "%"
autoHide: false autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide"
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery)) disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
tooltipText: { tooltipText: {
let lines = [] let lines = []
@ -113,8 +110,7 @@ Item {
if (battery.changeRate !== undefined) { if (battery.changeRate !== undefined) {
const rate = battery.changeRate const rate = battery.changeRate
if (rate > 0) { if (rate > 0) {
lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed( lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed(2) + " W.")
2) + " W.")
} else if (rate < 0) { } else if (rate < 0) {
lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W.") lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W.")
} else { } else {

View file

@ -21,5 +21,6 @@ NIconButton {
icon: Settings.data.network.bluetoothEnabled ? "bluetooth" : "bluetooth-off" icon: Settings.data.network.bluetoothEnabled ? "bluetooth" : "bluetooth-off"
tooltipText: "Bluetooth devices." tooltipText: "Bluetooth devices."
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(screen, this) onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
} }

View file

@ -13,13 +13,12 @@ Item {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -29,8 +28,8 @@ Item {
return {} return {}
} }
readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
// Used to avoid opening the pill on Quickshell startup // Used to avoid opening the pill on Quickshell startup
property bool firstBrightnessReceived: false property bool firstBrightnessReceived: false
@ -82,15 +81,16 @@ Item {
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: { text: {
var monitor = getMonitor() var monitor = getMonitor()
return monitor ? (Math.round(monitor.brightness * 100) + "%") : "" return monitor ? Math.round(monitor.brightness * 100) : ""
} }
forceOpen: userAlwaysShowPercentage suffix: text.length > 0 ? "%" : "-"
forceOpen: displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide"
tooltipText: { tooltipText: {
var monitor = getMonitor() var monitor = getMonitor()
if (!monitor) if (!monitor)
return "" return ""
return "Brightness: " + Math.round(monitor.brightness * 100) + "%\nMethod: " + monitor.method return "Brightness: " + Math.round(monitor.brightness * 100) + "%\nRight click for settings.\nScroll to modify brightness."
+ "\nLeft click for advanced settings.\nScroll up/down to change brightness."
} }
onWheel: function (angle) { onWheel: function (angle) {
@ -106,8 +106,14 @@ Item {
onClicked: { onClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Brightness settingsPanel.requestedTab = SettingsPanel.Tab.Display
settingsPanel.open(screen) settingsPanel.open()
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display
settingsPanel.open()
} }
} }
} }

View file

@ -1,4 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Services import qs.Services
@ -12,13 +13,12 @@ Rectangle {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -28,45 +28,178 @@ Rectangle {
return {} return {}
} }
// Resolve settings: try user settings or defaults from BarWidgetRegistry readonly property string barPosition: Settings.data.bar.position
readonly property bool showDate: widgetSettings.showDate !== undefined ? widgetSettings.showDate : widgetMetadata.showDate
readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
readonly property bool showSeconds: widgetSettings.showSeconds !== undefined ? widgetSettings.showSeconds : widgetMetadata.showSeconds
readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth
!== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
implicitWidth: clock.width + Style.marginM * 2 * scaling // Resolve settings: try user settings or defaults from BarWidgetRegistry
implicitHeight: Math.round(Style.capsuleHeight * scaling) readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
radius: Math.round(Style.radiusM * scaling) readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth !== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
readonly property string displayFormat: widgetSettings.displayFormat !== undefined ? widgetSettings.displayFormat : widgetMetadata.displayFormat
// Use compact mode for vertical bars
readonly property bool verticalMode: barPosition === "left" || barPosition === "right"
implicitWidth: verticalMode ? Math.round(Style.capsuleHeight * scaling) : Math.round(layout.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: verticalMode ? Math.round(Style.capsuleHeight * 2.5 * scaling) : Math.round(Style.baseWidgetSize * 0.8 * scaling) // Match NPill
radius: Math.round(Style.radiusS * scaling)
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
// Clock Icon with attached calendar Item {
NText { id: clockContainer
id: clock anchors.fill: parent
text: { anchors.margins: Style.marginXS * scaling
const now = Time.date
const timeFormat = use12h ? (showSeconds ? "h:mm:ss AP" : "h:mm AP") : (showSeconds ? "HH:mm:ss" : "HH:mm")
const timeString = Qt.formatDateTime(now, timeFormat)
if (showDate) { ColumnLayout {
let dayName = now.toLocaleDateString(Qt.locale(), "ddd") id: layout
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) anchors.centerIn: parent
let day = now.getDate() spacing: verticalMode ? -2 * scaling : -3 * scaling
let month = now.toLocaleDateString(Qt.locale(), "MMM")
return timeString + " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`) // Compact mode for vertical bars - Time section (HH, MM)
Repeater {
model: verticalMode ? 2 : 1
NText {
readonly property bool showSeconds: (displayFormat === "time-seconds")
readonly property bool inlineDate: (displayFormat === "time-date")
readonly property var now: Time.date
text: {
if (verticalMode) {
// Compact mode: time section (first 2 lines)
switch (index) {
case 0:
// Hours
if (use12h) {
const hours = now.getHours()
const displayHours = hours === 0 ? 12 : (hours > 12 ? hours - 12 : hours)
return displayHours.toString().padStart(2, '0')
} else {
return now.getHours().toString().padStart(2, '0')
}
case 1:
// Minutes
return now.getMinutes().toString().padStart(2, '0')
default:
return ""
}
} else {
// Normal mode: single line with time
let timeStr = ""
if (use12h) {
// 12-hour format with proper padding and consistent spacing
const hours = now.getHours()
const displayHours = hours === 0 ? 12 : (hours > 12 ? hours - 12 : hours)
const paddedHours = displayHours.toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
const ampm = hours < 12 ? 'AM' : 'PM'
if (showSeconds) {
const seconds = now.getSeconds().toString().padStart(2, '0')
timeStr = `${paddedHours}:${minutes}:${seconds} ${ampm}`
} else {
timeStr = `${paddedHours}:${minutes} ${ampm}`
}
} else {
// 24-hour format with padding
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
if (showSeconds) {
const seconds = now.getSeconds().toString().padStart(2, '0')
timeStr = `${hours}:${minutes}:${seconds}`
} else {
timeStr = `${hours}:${minutes}`
}
}
// Add inline date if needed
if (inlineDate) {
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
const day = now.getDate().toString().padStart(2, '0')
let month = now.toLocaleDateString(Qt.locale(), "MMM")
timeStr += " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
}
return timeStr
}
}
font.family: Settings.data.ui.fontFixed
font.pointSize: verticalMode ? Style.fontSizeXXS * scaling : Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
// Separator line for compact mode (between time and date)
Rectangle {
visible: verticalMode
Layout.preferredWidth: 20 * scaling
Layout.preferredHeight: 2 * scaling
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 3 * scaling
Layout.bottomMargin: 3 * scaling
color: Color.mPrimary
opacity: 0.3
radius: 1 * scaling
}
// Compact mode for vertical bars - Date section (DD, MM)
Repeater {
model: verticalMode ? 2 : 0
NText {
readonly property var now: Time.date
text: {
if (verticalMode) {
// Compact mode: date section (last 2 lines)
switch (index) {
case 0:
// Day
return now.getDate().toString().padStart(2, '0')
case 1:
// Month
return (now.getMonth() + 1).toString().padStart(2, '0')
default:
return ""
}
}
return ""
}
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
// Second line for normal mode (date)
NText {
visible: !verticalMode && (displayFormat === "time-date-short")
text: {
const now = Time.date
const day = now.getDate().toString().padStart(2, '0')
const month = (now.getMonth() + 1).toString().padStart(2, '0')
return reverseDayMonth ? `${month}/${day}` : `${day}/${month}`
}
// Enable fixed-width font for consistent spacing
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightRegular
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
} }
return timeString
} }
anchors.centerIn: parent
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
} }
NTooltip { NTooltip {
id: tooltip id: tooltip
text: `${Time.formatDate(reverseDayMonth)}.` text: `${Time.formatDate(reverseDayMonth)}.`
target: clock target: clockContainer
positionAbove: Settings.data.bar.position === "bottom" positionAbove: Settings.data.bar.position === "bottom"
} }
@ -85,7 +218,7 @@ Rectangle {
} }
onClicked: { onClicked: {
tooltip.hide() tooltip.hide()
PanelService.getPanel("calendarPanel")?.toggle(screen, this) PanelService.getPanel("calendarPanel")?.toggle(this)
} }
} }
} }

View file

@ -1,12 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io
import qs.Commons import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.SettingsPanel import qs.Modules.SettingsPanel
NIconButton { Item {
id: root id: root
// Widget properties passed from Bar.qml // Widget properties passed from Bar.qml
@ -15,13 +16,12 @@ NIconButton {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -36,33 +36,80 @@ NIconButton {
readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
readonly property string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "")
readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000)
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec) readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
enabled: hasExec implicitWidth: pill.width
allowClickWhenDisabled: true // we want to be able to open config with left click when its not setup properly implicitHeight: pill.height
colorBorder: Color.transparent
colorBorderHover: Color.transparent NPill {
sizeRatio: 0.8 id: pill
icon: customIcon
tooltipText: { rightOpen: BarWidgetRegistry.getNPillDirection(root)
if (!hasExec) { icon: customIcon
return "Custom Button - Configure in settings" text: _dynamicText
} else { autoHide: false
var lines = [] forceOpen: _dynamicText !== ""
if (leftClickExec !== "") { forceClose: false
lines.push(`Left click: <i>${leftClickExec}</i>.`) disableOpen: true
tooltipText: {
if (!hasExec) {
return "Custom Button - Configure in settings"
} else {
var lines = []
if (leftClickExec !== "") {
lines.push(`Left click: ${leftClickExec}.`)
}
if (rightClickExec !== "") {
lines.push(`Right click: ${rightClickExec}.`)
}
if (middleClickExec !== "") {
lines.push(`Middle click: ${middleClickExec}.`)
}
return lines.join("\n")
} }
if (rightClickExec !== "") { }
lines.push(`Right click: <i>${rightClickExec}</i>.`)
} onClicked: root.onClicked()
if (middleClickExec !== "") { onRightClicked: root.onRightClicked()
lines.push(`Middle click: <i>${middleClickExec}</i>.`) onMiddleClicked: root.onMiddleClicked()
} }
return lines.join("<br/>")
// Internal state for dynamic text
property string _dynamicText: ""
// Periodically run the text command (if set)
Timer {
id: refreshTimer
interval: Math.max(250, textIntervalMs)
repeat: true
running: (textCommand && textCommand.length > 0)
triggeredOnStart: true
onTriggered: {
if (!textCommand || textCommand.length === 0)
return
if (textProc.running)
return
textProc.command = ["sh", "-lc", textCommand]
textProc.running = true
} }
} }
onClicked: { Process {
id: textProc
stdout: StdioCollector {}
stderr: StdioCollector {}
onExited: (exitCode, exitStatus) => {
var out = String(stdout.text || "").trim()
if (out.indexOf("\n") !== -1) {
out = out.split("\n")[0]
}
_dynamicText = out
}
}
function onClicked() {
if (leftClickExec) { if (leftClickExec) {
Quickshell.execDetached(["sh", "-c", leftClickExec]) Quickshell.execDetached(["sh", "-c", leftClickExec])
Logger.log("CustomButton", `Executing command: ${leftClickExec}`) Logger.log("CustomButton", `Executing command: ${leftClickExec}`)
@ -70,18 +117,18 @@ NIconButton {
// No script was defined, open settings // No script was defined, open settings
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Bar settingsPanel.requestedTab = SettingsPanel.Tab.Bar
settingsPanel.open(screen) settingsPanel.open()
} }
} }
onRightClicked: { function onRightClicked() {
if (rightClickExec) { if (rightClickExec) {
Quickshell.execDetached(["sh", "-c", rightClickExec]) Quickshell.execDetached(["sh", "-c", rightClickExec])
Logger.log("CustomButton", `Executing command: ${rightClickExec}`) Logger.log("CustomButton", `Executing command: ${rightClickExec}`)
} }
} }
onMiddleClicked: { function onMiddleClicked() {
if (middleClickExec) { if (middleClickExec) {
Quickshell.execDetached(["sh", "-c", middleClickExec]) Quickshell.execDetached(["sh", "-c", middleClickExec])
Logger.log("CustomButton", `Executing command: ${middleClickExec}`) Logger.log("CustomButton", `Executing command: ${middleClickExec}`)

View file

@ -17,7 +17,5 @@ NIconButton {
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
anchors.verticalCenter: parent.verticalCenter
onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode
} }

View file

@ -13,6 +13,25 @@ Item {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
// Use the shared service for keyboard layout // Use the shared service for keyboard layout
property string currentLayout: KeyboardLayoutService.currentLayout property string currentLayout: KeyboardLayoutService.currentLayout
@ -26,9 +45,10 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: "keyboard" icon: "keyboard"
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: currentLayout text: currentLayout.toUpperCase()
tooltipText: "Keyboard layout: " + currentLayout tooltipText: "Keyboard layout: " + currentLayout.toUpperCase()
forceOpen: root.displayMode === "forceOpen"
forceClose: root.displayMode === "alwaysHide"
onClicked: { onClicked: {
// You could open keyboard settings here if needed // You could open keyboard settings here if needed

View file

@ -7,7 +7,7 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
RowLayout { Item {
id: root id: root
property ShellScreen screen property ShellScreen screen
@ -15,13 +15,12 @@ RowLayout {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -31,12 +30,11 @@ RowLayout {
return {} return {}
} }
readonly property bool showAlbumArt: (widgetSettings.showAlbumArt readonly property string barPosition: Settings.data.bar.position
!== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
readonly property bool showVisualizer: (widgetSettings.showVisualizer readonly property bool showAlbumArt: (widgetSettings.showAlbumArt !== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
!== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer readonly property bool showVisualizer: (widgetSettings.showVisualizer !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
!== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
// 6% of total width // 6% of total width
readonly property real minWidth: Math.max(1, screen.width * 0.06) readonly property real minWidth: Math.max(1, screen.width * 0.06)
@ -46,10 +44,26 @@ RowLayout {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
} }
Layout.alignment: Qt.AlignVCenter function calculatedVerticalHeight() {
spacing: Style.marginS * scaling return Math.round(Style.baseWidgetSize * 0.8 * scaling)
}
function calculatedHorizontalWidth() {
let total = Style.marginM * 2 * scaling // internal padding
if (showAlbumArt) {
total += 18 * scaling + 2 * scaling // album art + spacing
} else {
total += Style.fontSizeL * scaling + 2 * scaling // icon + spacing
}
total += Math.min(fullTitleMetrics.contentWidth, maxWidth * scaling) // title text
// Row layout handles spacing between widgets
return total
}
implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0
implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (rowLayout.implicitWidth + Style.marginM * 2 * scaling)) : 0
visible: MediaService.currentPlayer !== null && MediaService.canPlay visible: MediaService.currentPlayer !== null && MediaService.canPlay
Layout.preferredWidth: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0
// A hidden text element to safely measure the full title width // A hidden text element to safely measure the full title width
NText { NText {
@ -61,12 +75,12 @@ RowLayout {
Rectangle { Rectangle {
id: mediaMini id: mediaMini
visible: root.visible
Layout.preferredWidth: rowLayout.implicitWidth + Style.marginM * 2 * scaling anchors.left: parent.left
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignVCenter width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (rowLayout.implicitWidth + Style.marginM * 2 * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling) radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
// Used to anchor the tooltip, so the tooltip does not move when the content expands // Used to anchor the tooltip, so the tooltip does not move when the content expands
@ -79,8 +93,8 @@ RowLayout {
Item { Item {
id: mainContainer id: mainContainer
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
Loader { Loader {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -127,10 +141,12 @@ RowLayout {
} }
} }
// Horizontal layout for top/bottom bars
RowLayout { RowLayout {
id: rowLayout id: rowLayout
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: barPosition === "top" || barPosition === "bottom"
z: 1 // Above the visualizer z: 1 // Above the visualizer
NIcon { NIcon {
@ -191,6 +207,33 @@ RowLayout {
} }
} }
// Vertical layout for left/right bars - icon only
Item {
id: verticalLayout
anchors.centerIn: parent
width: parent.width - Style.marginM * scaling * 2
height: parent.height - Style.marginM * scaling * 2
visible: barPosition === "left" || barPosition === "right"
z: 1 // Above the visualizer
// Media icon
Item {
width: Style.baseWidgetSize * 0.5 * scaling
height: Style.baseWidgetSize * 0.5 * scaling
anchors.centerIn: parent
visible: getTitle() !== ""
NIcon {
id: mediaIconVertical
anchors.fill: parent
icon: MediaService.isPlaying ? "media-pause" : "media-play"
font.pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
}
// Mouse area for hover detection // Mouse area for hover detection
MouseArea { MouseArea {
id: mouseArea id: mouseArea
@ -213,12 +256,18 @@ RowLayout {
} }
onEntered: { onEntered: {
if (tooltip.text !== "") { if (barPosition === "left" || barPosition === "right") {
tooltip.show()
} else if (tooltip.text !== "") {
tooltip.show() tooltip.show()
} }
} }
onExited: { onExited: {
tooltip.hide() if (barPosition === "left" || barPosition === "right") {
tooltip.hide()
} else {
tooltip.hide()
}
} }
} }
} }
@ -227,16 +276,23 @@ RowLayout {
NTooltip { NTooltip {
id: tooltip id: tooltip
text: { text: {
var str = "" if (barPosition === "left" || barPosition === "right") {
if (MediaService.canGoNext) { return getTitle()
str += "Right click for next.\n" } else {
var str = ""
if (MediaService.canGoNext) {
str += "Right click for next.\n"
}
if (MediaService.canGoPrevious) {
str += "Middle click for previous."
}
return str
} }
if (MediaService.canGoPrevious) {
str += "Middle click for previous."
}
return str
} }
target: anchor target: (barPosition === "left" || barPosition === "right") ? verticalLayout : anchor
positionLeft: barPosition === "right"
positionRight: barPosition === "left"
positionAbove: Settings.data.bar.position === "bottom" positionAbove: Settings.data.bar.position === "bottom"
delay: 500
} }
} }

View file

@ -15,13 +15,12 @@ Item {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -31,8 +30,8 @@ Item {
return {} return {}
} }
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
// Used to avoid opening the pill on Quickshell startup // Used to avoid opening the pill on Quickshell startup
property bool firstInputVolumeReceived: false property bool firstInputVolumeReceived: false
@ -89,14 +88,14 @@ Item {
NPill { NPill {
id: pill id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon() icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.inputVolume * 100) + "%" text: Math.floor(AudioService.inputVolume * 100)
forceOpen: alwaysShowPercentage suffix: "%"
tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100) forceOpen: displayMode === "alwaysShow"
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." forceClose: displayMode === "alwaysHide"
tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100) + "%\nLeft click to toggle mute.\nRight click for settings.\nScroll to modify volume."
onWheel: function (delta) { onWheel: function (delta) {
wheelAccumulator += delta wheelAccumulator += delta
@ -109,12 +108,12 @@ Item {
} }
} }
onClicked: { onClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") AudioService.setInputMuted(!AudioService.inputMuted)
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open(screen)
} }
onRightClicked: { onRightClicked: {
AudioService.setInputMuted(!AudioService.inputMuted) var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open()
} }
onMiddleClicked: { onMiddleClicked: {
Quickshell.execDetached(["pwvucontrol"]) Quickshell.execDetached(["pwvucontrol"])

View file

@ -15,8 +15,8 @@ NIconButton {
property real scaling: 1.0 property real scaling: 1.0
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? Color.mTertiary : Color.mPrimary) : Color.mSurfaceVariant colorBg: Settings.data.nightLight.forced ? Color.mPrimary : Color.mSurfaceVariant
colorFg: Settings.data.nightLight.enabled ? Color.mOnPrimary : Color.mOnSurface colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
@ -36,7 +36,7 @@ NIconButton {
onRightClicked: { onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Brightness settingsPanel.requestedTab = SettingsPanel.Tab.Display
settingsPanel.open(screen) settingsPanel.open()
} }
} }

View file

@ -15,13 +15,12 @@ NIconButton {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -30,10 +29,8 @@ NIconButton {
} }
return {} return {}
} }
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
!== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero
!== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
function lastSeenTs() { function lastSeenTs() {
return Settings.data.notifications?.lastSeenTs || 0 return Settings.data.notifications?.lastSeenTs || 0
@ -62,7 +59,7 @@ NIconButton {
onClicked: { onClicked: {
var panel = PanelService.getPanel("notificationHistoryPanel") var panel = PanelService.getPanel("notificationHistoryPanel")
panel?.toggle(screen, this) panel?.toggle(this)
Settings.data.notifications.lastSeenTs = Time.timestamp * 1000 Settings.data.notifications.lastSeenTs = Time.timestamp * 1000
} }

View file

@ -19,5 +19,5 @@ NIconButton {
colorFg: Color.mError colorFg: Color.mError
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
onClicked: PanelService.getPanel("powerPanel")?.toggle(screen) onClicked: PanelService.getPanel("powerPanel")?.toggle()
} }

View file

@ -16,6 +16,5 @@ NIconButton {
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Color.mPrimary colorBg: Color.mPrimary
colorFg: Color.mOnPrimary colorFg: Color.mOnPrimary
anchors.verticalCenter: parent.verticalCenter
onClicked: ScreenRecorderService.toggleRecording() onClicked: ScreenRecorderService.toggleRecording()
} }

View file

@ -14,13 +14,12 @@ NIconButton {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -30,39 +29,27 @@ NIconButton {
return {} return {}
} }
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo readonly property bool useDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
icon: useDistroLogo ? "" : "apps" icon: useDistroLogo ? "" : "noctalia"
tooltipText: "Open side panel." tooltipText: "Open side panel."
sizeRatio: 0.8 sizeRatio: 0.85
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface colorFg: Color.mOnSurface
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mTertiary
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: useDistroLogo ? Color.mTertiary : Color.transparent
onClicked: PanelService.getPanel("sidePanel")?.toggle(this)
anchors.verticalCenter: parent.verticalCenter onRightClicked: PanelService.getPanel("settingsPanel")?.toggle()
onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this)
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen)
IconImage { IconImage {
id: logo id: logo
anchors.centerIn: parent anchors.centerIn: parent
width: root.width * 0.6 width: root.width * 0.85
height: width height: width
source: useDistroLogo ? DistroLogoService.osLogo : "" source: useDistroLogo ? DistroLogoService.osLogo : ""
visible: useDistroLogo && source !== "" visible: useDistroLogo && source !== ""
smooth: true smooth: true
} }
MultiEffect {
anchors.fill: logo
source: logo
//visible: logo.visible
colorization: 1
brightness: 1
saturation: 1
colorizationColor: root.hovering ? Color.mSurfaceVariant : Color.mOnSurface
}
} }

View file

@ -14,13 +14,12 @@ Item {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {

View file

@ -5,7 +5,7 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
RowLayout { Rectangle {
id: root id: root
property ShellScreen screen property ShellScreen screen
@ -13,13 +13,12 @@ RowLayout {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -29,245 +28,419 @@ RowLayout {
return {} return {}
} }
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage readonly property string barPosition: Settings.data.bar.position
!== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
!== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
!== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats
!== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage
!== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
readonly property bool showGpuTemp: (widgetSettings.showGpuTemp !== undefined) ? widgetSettings.showGpuTemp : (widgetMetadata.showGpuTemp
|| false)
Layout.alignment: Qt.AlignVCenter anchors.centerIn: parent
spacing: Style.marginS * scaling implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: (barPosition === "left" || barPosition === "right") ? Math.round(verticalLayout.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
Rectangle { // Compact speed formatter for vertical bar display
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) function formatCompactSpeed(bytesPerSecond) {
Layout.preferredWidth: mainLayout.implicitWidth + Style.marginM * scaling * 2 if (!bytesPerSecond || bytesPerSecond <= 0)
Layout.alignment: Qt.AlignVCenter return "0"
const units = ["", "k", "M", "G"]
let value = bytesPerSecond
let unitIndex = 0
while (value >= 1024 && unitIndex < units.length - 1) {
value = value / 1024.0
unitIndex++
}
// Promote at ~100 of current unit (e.g., 100k -> ~0.1M shown as 0.1M or 0M if rounded)
if (unitIndex < units.length - 1 && value >= 100) {
value = value / 1024.0
unitIndex++
}
const display = (value >= 10) ? Math.round(value).toString() : value.toFixed(1)
return display + units[unitIndex]
}
radius: Math.round(Style.radiusM * scaling) // Horizontal layout for top/bottom bars
color: Color.mSurfaceVariant RowLayout {
id: horizontalLayout
anchors.centerIn: parent
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
spacing: Style.marginXS * scaling
visible: barPosition === "top" || barPosition === "bottom"
RowLayout { // CPU Usage Component
id: mainLayout Item {
anchors.centerIn: parent // Better centering than margins Layout.preferredWidth: cpuUsageRow.implicitWidth
width: parent.width - Style.marginM * scaling * 2 Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
spacing: Style.marginS * scaling Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage
// CPU Usage Component RowLayout {
Item { id: cpuUsageRow
Layout.preferredWidth: cpuUsageRow.implicitWidth anchors.centerIn: parent
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) spacing: Style.marginXXS * scaling
Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage
RowLayout { NIcon {
id: cpuUsageRow icon: "cpu-usage"
anchors.centerIn: parent font.pointSize: Style.fontSizeM * scaling
spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter
}
NIcon { NText {
icon: "cpu-usage" text: `${SystemStatService.cpuUsage}%`
font.pointSize: Style.fontSizeM * scaling font.family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignVCenter font.pointSize: Style.fontSizeXS * scaling
} font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
NText { verticalAlignment: Text.AlignVCenter
text: `${SystemStatService.cpuUsage}%` color: Color.mPrimary
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
} }
} }
}
// CPU Temperature Component // CPU Temperature Component
Item { Item {
Layout.preferredWidth: cpuTempRow.implicitWidth Layout.preferredWidth: cpuTempRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showCpuTemp visible: showCpuTemp
RowLayout { RowLayout {
id: cpuTempRow id: cpuTempRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXXS * scaling
NIcon { NIcon {
icon: "cpu-temperature" icon: "cpu-temperature"
// Fire is so tall, we need to make it smaller // Fire is so tall, we need to make it smaller
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NText { NText {
text: `${SystemStatService.cpuTemp}°C` text: `${SystemStatService.cpuTemp}°C`
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Color.mPrimary color: Color.mPrimary
}
} }
} }
}
// GPU Temperature Component // Memory Usage Component
Item { Item {
Layout.preferredWidth: gpuTempRow.implicitWidth Layout.preferredWidth: memoryUsageRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showGpuTemp visible: showMemoryUsage
RowLayout { RowLayout {
id: gpuTempRow id: memoryUsageRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXXS * scaling
NIcon { NIcon {
icon: "gpu-temperature" icon: "memory"
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NText { NText {
text: `${SystemStatService.gpuTemp}°C` text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Color.mPrimary color: Color.mPrimary
}
} }
} }
}
// Memory Usage Component // Network Download Speed Component
Item { Item {
Layout.preferredWidth: memoryUsageRow.implicitWidth Layout.preferredWidth: networkDownloadRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showMemoryUsage visible: showNetworkStats
RowLayout { RowLayout {
id: memoryUsageRow id: networkDownloadRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
NIcon { NIcon {
icon: "memory" icon: "download-speed"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NText { NText {
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G` text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Color.mPrimary color: Color.mPrimary
}
} }
} }
}
// Network Download Speed Component // Network Upload Speed Component
Item { Item {
Layout.preferredWidth: networkDownloadRow.implicitWidth Layout.preferredWidth: networkUploadRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showNetworkStats visible: showNetworkStats
RowLayout { RowLayout {
id: networkDownloadRow id: networkUploadRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
NIcon { NIcon {
icon: "download-speed" icon: "upload-speed"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NText { NText {
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed) text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Color.mPrimary color: Color.mPrimary
}
} }
} }
}
// Network Upload Speed Component // Disk Usage Component (primary drive)
Item { Item {
Layout.preferredWidth: networkUploadRow.implicitWidth Layout.preferredWidth: diskUsageRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showNetworkStats visible: showDiskUsage
RowLayout { RowLayout {
id: networkUploadRow id: diskUsageRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
NIcon { NIcon {
icon: "upload-speed" icon: "storage"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NText { NText {
text: SystemStatService.formatSpeed(SystemStatService.txSpeed) text: `${SystemStatService.diskPercent}%`
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Color.mPrimary color: Color.mPrimary
}
} }
} }
}
}
// Disk Usage Component (primary drive) // Vertical layout for left/right bars
Item { ColumnLayout {
Layout.preferredWidth: diskUsageRow.implicitWidth id: verticalLayout
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) anchors.centerIn: parent
Layout.alignment: Qt.AlignVCenter anchors.topMargin: Style.marginS * scaling
visible: showDiskUsage anchors.bottomMargin: Style.marginS * scaling
width: Math.round(28 * scaling)
spacing: Style.marginS * scaling
visible: barPosition === "left" || barPosition === "right"
RowLayout { // CPU Usage Component
id: diskUsageRow Item {
anchors.centerIn: parent Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
spacing: Style.marginXS * scaling Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showCpuUsage
NIcon { Column {
icon: "storage" id: cpuUsageRowVertical
font.pointSize: Style.fontSizeM * scaling anchors.centerIn: parent
Layout.alignment: Qt.AlignVCenter spacing: Style.marginXXS * scaling
}
NText { NText {
text: `${SystemStatService.diskPercent}%` text: `${Math.round(SystemStatService.cpuUsage)}%`
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter anchors.horizontalCenter: parent.horizontalCenter
verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary color: Color.mPrimary
} }
NIcon {
icon: "cpu-usage"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// CPU Temperature Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showCpuTemp
Column {
id: cpuTempRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
text: `${SystemStatService.cpuTemp}°`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary
}
NIcon {
icon: "cpu-temperature"
// Fire is so tall, we need to make it smaller
font.pointSize: Style.fontSizeXS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Memory Usage Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showMemoryUsage
Column {
id: memoryUsageRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${Math.round(SystemStatService.memGb)}G`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary
}
NIcon {
icon: "memory"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Network Download Speed Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showNetworkStats
Column {
id: networkDownloadRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: formatCompactSpeed(SystemStatService.rxSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
color: Color.mPrimary
}
NIcon {
icon: "download-speed"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Network Upload Speed Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showNetworkStats
Column {
id: networkUploadRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: formatCompactSpeed(SystemStatService.txSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
color: Color.mPrimary
}
NIcon {
icon: "upload-speed"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Disk Usage Component (primary drive)
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showDiskUsage
ColumnLayout {
id: diskUsageRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
text: `${SystemStatService.diskPercent}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary
}
NIcon {
icon: "storage"
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignHCenter
} }
} }
} }

View file

@ -17,36 +17,37 @@ Rectangle {
property real scaling: 1.0 property real scaling: 1.0
readonly property real itemSize: 24 * scaling readonly property real itemSize: 24 * scaling
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
function onLoaded() { function onLoaded() {
// When the widget is fully initialized with its props // When the widget is fully initialized with its props set the screen for the trayMenu
// set the screen for the trayMenu
if (trayMenu.item) { if (trayMenu.item) {
trayMenu.item.screen = screen trayMenu.item.screen = screen
} }
} }
visible: SystemTray.items.values.length > 0 visible: SystemTray.items.values.length > 0
implicitWidth: trayLayout.implicitWidth + Style.marginM * scaling * 2 implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2)
implicitHeight: Math.round(Style.capsuleHeight * scaling) implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling) radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
RowLayout { Flow {
id: trayLayout id: trayFlow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
Repeater { Repeater {
id: repeater id: repeater
model: SystemTray.items model: SystemTray.items
delegate: Item { delegate: Item {
Layout.preferredWidth: itemSize width: itemSize
Layout.preferredHeight: itemSize height: itemSize
Layout.alignment: Qt.AlignCenter
visible: modelData visible: modelData
IconImage { IconImage {
@ -111,9 +112,21 @@ Rectangle {
if (modelData.hasMenu && modelData.menu && trayMenu.item) { if (modelData.hasMenu && modelData.menu && trayMenu.item) {
trayPanel.open() trayPanel.open()
// Anchor the menu to the tray icon item (parent) and position it below the icon // Position menu based on bar position
const menuX = (width / 2) - (trayMenu.item.width / 2) let menuX, menuY
const menuY = Math.round(Style.barHeight * scaling) if (barPosition === "left") {
// For left bar: position menu to the right of the bar
menuX = width + Style.marginM * scaling
menuY = 0
} else if (barPosition === "right") {
// For right bar: position menu to the left of the bar
menuX = -trayMenu.item.width - Style.marginM * scaling
menuY = 0
} else {
// For horizontal bars: center horizontally and position below
menuX = (width / 2) - (trayMenu.item.width / 2)
menuY = Math.round(Style.barHeight * scaling)
}
trayMenu.item.menu = modelData.menu trayMenu.item.menu = modelData.menu
trayMenu.item.showAt(parent, menuX, menuY) trayMenu.item.showAt(parent, menuX, menuY)
} else { } else {

View file

@ -15,13 +15,12 @@ Item {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -31,8 +30,8 @@ Item {
return {} return {}
} }
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
// Used to avoid opening the pill on Quickshell startup // Used to avoid opening the pill on Quickshell startup
property bool firstVolumeReceived: false property bool firstVolumeReceived: false
@ -78,10 +77,11 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon() icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.volume * 100) + "%" text: Math.floor(AudioService.volume * 100)
forceOpen: alwaysShowPercentage suffix: "%"
tooltipText: "Volume: " + Math.round(AudioService.volume * 100) forceOpen: displayMode === "alwaysShow"
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." forceClose: displayMode === "alwaysHide"
tooltipText: "Volume: " + Math.round(AudioService.volume * 100) + "%\nLeft click to toggle mute.\nRight click for settings.\nScroll to modify volume."
onWheel: function (delta) { onWheel: function (delta) {
wheelAccumulator += delta wheelAccumulator += delta
@ -94,12 +94,12 @@ Item {
} }
} }
onClicked: { onClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") AudioService.setOutputMuted(!AudioService.muted)
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open(screen)
} }
onRightClicked: { onRightClicked: {
AudioService.setMuted(!AudioService.muted) var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open()
} }
onMiddleClicked: { onMiddleClicked: {
Quickshell.execDetached(["pwvucontrol"]) Quickshell.execDetached(["pwvucontrol"])

View file

@ -41,5 +41,6 @@ NIconButton {
} }
} }
tooltipText: "Manage Wi-Fi." tooltipText: "Manage Wi-Fi."
onClicked: PanelService.getPanel("wifiPanel")?.toggle(screen, this) onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
onRightClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
} }

View file

@ -16,13 +16,12 @@ Item {
// Widget properties passed from Bar.qml for per-instance settings // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: "" property string widgetId: ""
property string barSection: "" property string section: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
@ -32,7 +31,10 @@ Item {
return {} return {}
} }
readonly property string barPosition: Settings.data.bar.position
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
property bool isDestroying: false property bool isDestroying: false
property bool hovered: false property bool hovered: false
@ -47,17 +49,8 @@ Item {
signal workspaceChanged(int workspaceId, color accentColor) signal workspaceChanged(int workspaceId, color accentColor)
implicitHeight: Math.round(Style.barHeight * scaling) implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)
implicitWidth: { implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.barHeight * scaling) : calculatedHorizontalWidth()
let total = 0
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
total += calculatedWsWidth(ws)
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
total += horizontalPadding * 2
return total
}
function calculatedWsWidth(ws) { function calculatedWsWidth(ws) {
if (ws.isFocused) if (ws.isFocused)
@ -68,6 +61,37 @@ Item {
return Math.round(20 * scaling) return Math.round(20 * scaling)
} }
function calculatedWsHeight(ws) {
if (ws.isFocused)
return Math.round(44 * scaling)
else if (ws.isActive)
return Math.round(28 * scaling)
else
return Math.round(20 * scaling)
}
function calculatedVerticalHeight() {
let total = 0
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
total += calculatedWsHeight(ws)
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
total += horizontalPadding * 2
return total
}
function calculatedHorizontalWidth() {
let total = 0
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
total += calculatedWsWidth(ws)
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
total += horizontalPadding * 2
return total
}
Component.onCompleted: { Component.onCompleted: {
refreshWorkspaces() refreshWorkspaces()
} }
@ -77,6 +101,7 @@ Item {
} }
onScreenChanged: refreshWorkspaces() onScreenChanged: refreshWorkspaces()
onHideUnoccupiedChanged: refreshWorkspaces()
Connections { Connections {
target: WorkspaceService target: WorkspaceService
@ -91,11 +116,15 @@ Item {
for (var i = 0; i < WorkspaceService.workspaces.count; i++) { for (var i = 0; i < WorkspaceService.workspaces.count; i++) {
const ws = WorkspaceService.workspaces.get(i) const ws = WorkspaceService.workspaces.get(i)
if (ws.output.toLowerCase() === screen.name.toLowerCase()) { if (ws.output.toLowerCase() === screen.name.toLowerCase()) {
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) {
continue
}
localWorkspaces.append(ws) localWorkspaces.append(ws)
} }
} }
} }
workspaceRepeater.model = localWorkspaces workspaceRepeaterHorizontal.model = localWorkspaces
workspaceRepeaterVertical.model = localWorkspaces
updateWorkspaceFocus() updateWorkspaceFocus()
} }
@ -144,9 +173,8 @@ Item {
Rectangle { Rectangle {
id: workspaceBackground id: workspaceBackground
width: parent.width width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : parent.width
height: (barPosition === "left" || barPosition === "right") ? parent.height : Math.round(Style.capsuleHeight * scaling)
height: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling) radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
@ -154,14 +182,17 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Horizontal layout for top/bottom bars
Row { Row {
id: pillRow id: pillRow
spacing: spacingBetweenPills spacing: spacingBetweenPills
anchors.verticalCenter: workspaceBackground.verticalCenter anchors.verticalCenter: workspaceBackground.verticalCenter
width: root.width - horizontalPadding * 2 width: root.width - horizontalPadding * 2
x: horizontalPadding x: horizontalPadding
visible: barPosition === "top" || barPosition === "bottom"
Repeater { Repeater {
id: workspaceRepeater id: workspaceRepeaterHorizontal
model: localWorkspaces model: localWorkspaces
Item { Item {
id: workspacePillContainer id: workspacePillContainer
@ -197,8 +228,6 @@ Item {
return Color.mOnError return Color.mOnError
if (model.isActive || model.isOccupied) if (model.isActive || model.isOccupied)
return Color.mOnSecondary return Color.mOnSecondary
if (model.isUrgent)
return Color.mOnError
return Color.mOnSurface return Color.mOnSurface
} }
@ -214,8 +243,6 @@ Item {
return Color.mError return Color.mError
if (model.isActive || model.isOccupied) if (model.isActive || model.isOccupied)
return Color.mSecondary return Color.mSecondary
if (model.isUrgent)
return Color.mError
return Color.mOutline return Color.mOutline
} }
@ -299,4 +326,149 @@ Item {
} }
} }
} }
// Vertical layout for left/right bars
Column {
id: pillColumn
spacing: spacingBetweenPills
anchors.horizontalCenter: workspaceBackground.horizontalCenter
height: root.height - horizontalPadding * 2
y: horizontalPadding
visible: barPosition === "left" || barPosition === "right"
Repeater {
id: workspaceRepeaterVertical
model: localWorkspaces
Item {
id: workspacePillContainerVertical
width: (labelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling)
height: root.calculatedWsHeight(model)
Rectangle {
id: pillVertical
anchors.fill: parent
Loader {
active: (labelMode !== "none")
sourceComponent: Component {
Text {
x: (pillVertical.width - width) / 2
y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2
text: {
if (labelMode === "name" && model.name && model.name.length > 0) {
return model.name.substring(0, 2)
} else {
return model.idx.toString()
}
}
font.pointSize: model.isFocused ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
font.capitalization: Font.AllUppercase
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightBold
wrapMode: Text.Wrap
color: {
if (model.isFocused)
return Color.mOnPrimary
if (model.isUrgent)
return Color.mOnError
if (model.isActive || model.isOccupied)
return Color.mOnSecondary
return Color.mOnSurface
}
}
}
}
radius: width * 0.5
color: {
if (model.isFocused)
return Color.mPrimary
if (model.isUrgent)
return Color.mError
if (model.isActive || model.isOccupied)
return Color.mSecondary
return Color.mOutline
}
scale: model.isFocused ? 1.0 : 0.9
z: 0
MouseArea {
id: pillMouseAreaVertical
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
WorkspaceService.switchToWorkspace(model.idx)
}
hoverEnabled: true
}
// Material 3-inspired smooth animation for width, height, scale, color, opacity, and radius
Behavior on width {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on height {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutCubic
}
}
Behavior on radius {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
}
Behavior on width {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on height {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
// Burst effect overlay for focused pill (smaller outline)
Rectangle {
id: pillBurstVertical
anchors.centerIn: workspacePillContainerVertical
width: workspacePillContainerVertical.width + 18 * root.masterProgress * scale
height: workspacePillContainerVertical.height + 18 * root.masterProgress * scale
radius: width / 2
color: Color.transparent
border.color: root.effectColor
border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress)) * scaling))
opacity: root.effectsActive && model.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0
visible: root.effectsActive && model.isFocused
z: 1
}
}
}
}
} }

View file

@ -116,8 +116,7 @@ ColumnLayout {
NText { NText {
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
text: (modelData.signalStrength !== undefined text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
&& modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
color: getContentColor(Color.mOnSurface) color: getContentColor(Color.mOnSurface)
} }

View file

@ -11,8 +11,9 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
panelWidth: 380 * scaling preferredWidth: 380
panelHeight: 500 * scaling preferredHeight: 500
panelKeyboardFocus: true
panelContent: Rectangle { panelContent: Rectangle {
color: Color.transparent color: Color.transparent
@ -42,7 +43,7 @@ NPanel {
} }
NToggle { NToggle {
id: wifiSwitch id: bluetoothSwitch
checked: Settings.data.network.bluetoothEnabled checked: Settings.data.network.bluetoothEnabled
onToggled: checked => BluetoothService.setBluetoothEnabled(checked) onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
baseSize: Style.baseWidgetSize * 0.65 * scaling baseSize: Style.baseWidgetSize * 0.65 * scaling
@ -62,7 +63,7 @@ NPanel {
NIconButton { NIconButton {
icon: "close" icon: "close"
tooltipText: "Close" tooltipText: "Close."
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: {
root.close() root.close()
@ -75,7 +76,7 @@ NPanel {
} }
Rectangle { Rectangle {
visible: !Settings.data.network.bluetoothEnabled visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
color: Color.transparent color: Color.transparent
@ -108,12 +109,12 @@ NPanel {
} }
} }
ScrollView { NScrollView {
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff horizontalPolicy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded verticalPolicy: ScrollBar.AsNeeded
clip: true clip: true
contentWidth: availableWidth contentWidth: availableWidth
@ -142,8 +143,7 @@ NPanel {
property var items: { property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices) if (!BluetoothService.adapter || !Bluetooth.devices)
return [] return []
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted))
&& (dev.paired || dev.trusted))
return BluetoothService.sortDevices(filtered) return BluetoothService.sortDevices(filtered)
} }
model: items model: items
@ -175,10 +175,7 @@ NPanel {
} }
var availableCount = Bluetooth.devices.values.filter(dev => { var availableCount = Bluetooth.devices.values.filter(dev => {
return dev && !dev.paired && !dev.pairing return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0)
&& !dev.blocked
&& (dev.signalStrength === undefined
|| dev.signalStrength > 0)
}).length }).length
return (availableCount === 0) return (availableCount === 0)
} }

View file

@ -10,9 +10,9 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
panelWidth: 340 * scaling preferredWidth: 340
panelHeight: 320 * scaling preferredHeight: 320
panelAnchorRight: true panelAnchorRight: Settings.data.bar.position === "right"
// Main Column // Main Column
panelContent: ColumnLayout { panelContent: ColumnLayout {

View file

@ -12,10 +12,10 @@ import qs.Widgets
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
delegate: Loader { delegate: Item {
required property ShellScreen modelData required property ShellScreen modelData
property real scaling: ScalingService.getScreenScale(modelData) property real scaling: ScalingService.getScreenScale(modelData)
Connections { Connections {
target: ScalingService target: ScalingService
function onScaleChanged(screenName, scale) { function onScaleChanged(screenName, scale) {
@ -25,318 +25,368 @@ Variants {
} }
} }
active: Settings.isLoaded && modelData ? Settings.data.dock.monitors.includes(modelData.name) : false // Shared properties between peek and dock windows
readonly property bool autoHide: Settings.data.dock.autoHide
readonly property int hideDelay: 500
readonly property int showDelay: 100
readonly property int hideAnimationDuration: Style.animationFast
readonly property int showAnimationDuration: Style.animationFast
readonly property int peekHeight: 1 // no scaling for peek
readonly property int iconSize: 36 * scaling
readonly property int floatingMargin: Settings.data.dock.floatingRatio * Style.marginL * scaling
sourceComponent: PanelWindow { // Bar detection and positioning properties
id: dockWindow readonly property bool hasBar: modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false
readonly property bool barAtBottom: hasBar && Settings.data.bar.position === "bottom"
readonly property int barHeight: Style.barHeight * scaling
screen: modelData // Shared state between windows
property bool dockHovered: false
property bool anyAppHovered: false
property bool hidden: autoHide
property bool peekHovered: false
WlrLayershell.namespace: "noctalia-dock" // Separate property to control Loader - stays true during animations
property bool dockLoaded: !autoHide // Start loaded if autoHide is off
readonly property bool autoHide: Settings.data.dock.autoHide // Timer to unload dock after hide animation completes
readonly property int hideDelay: 500 Timer {
readonly property int showDelay: 100 id: unloadTimer
readonly property int hideAnimationDuration: Style.animationFast interval: hideAnimationDuration + 50 // Add small buffer
readonly property int showAnimationDuration: Style.animationFast onTriggered: {
readonly property int peekHeight: 7 * scaling if (hidden && autoHide) {
readonly property int fullHeight: dockContainer.height dockLoaded = false
readonly property int iconSize: 36 * scaling }
readonly property int floatingMargin: 12 * scaling // Margin to make dock float }
}
// Bar detection and positioning properties // Timer for auto-hide delay
readonly property bool hasBar: modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) Timer {
|| (Settings.data.bar.monitors.length === 0)) : false id: hideTimer
readonly property bool barAtBottom: hasBar && Settings.data.bar.position === "bottom" interval: hideDelay
readonly property bool barAtTop: hasBar && Settings.data.bar.position === "top" onTriggered: {
readonly property int barHeight: (barAtBottom || barAtTop) ? (Settings.data.bar.height || 30) * scaling : 0 if (autoHide && !dockHovered && !anyAppHovered && !peekHovered) {
readonly property int dockSpacing: 8 * scaling // Space between dock and bar/edge
// Track hover state
property bool dockHovered: false
property bool anyAppHovered: false
property bool hidden: autoHide
// Dock is positioned at the bottom
anchors.bottom: true
// Dock should be above bar but not create its own exclusion zone
exclusionMode: ExclusionMode.Ignore
focusable: false
// Make the window transparent
color: Color.transparent
// Set the window size - include extra height only if bar is at bottom
implicitWidth: dockContainer.width + (floatingMargin * 2)
implicitHeight: fullHeight + floatingMargin + (barAtBottom ? barHeight + dockSpacing : 0)
// Position the entire window above the bar only when bar is at bottom
margins.bottom: barAtBottom ? barHeight : 0
// Watch for autoHide setting changes
onAutoHideChanged: {
if (!autoHide) {
// If auto-hide is disabled, show the dock
hidden = false
hideTimer.stop()
showTimer.stop()
} else {
// If auto-hide is enabled, start hidden
hidden = true hidden = true
unloadTimer.restart() // Start unload timer when hiding
} }
} }
}
// Timer for auto-hide delay // Timer for show delay
Timer { Timer {
id: hideTimer id: showTimer
interval: hideDelay interval: showDelay
onTriggered: { onTriggered: {
if (autoHide && !dockHovered && !anyAppHovered && !peekArea.containsMouse) { if (autoHide) {
hidden = true dockLoaded = true // Load dock immediately
} hidden = false // Then trigger show animation
unloadTimer.stop() // Cancel any pending unload
} }
} }
}
// Timer for show delay // Watch for autoHide setting changes
Timer { onAutoHideChanged: {
id: showTimer if (!autoHide) {
interval: showDelay hidden = false
onTriggered: { dockLoaded = true
if (autoHide) { hideTimer.stop()
hidden = false showTimer.stop()
} unloadTimer.stop()
} } else {
hidden = true
unloadTimer.restart() // Schedule unload after animation
} }
}
// Peek area that remains visible when dock is hidden // PEEK WINDOW - Always visible when auto-hide is enabled
MouseArea { Loader {
id: peekArea active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && autoHide
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: peekHeight + floatingMargin + (barAtBottom ? dockSpacing : 0)
hoverEnabled: autoHide
visible: autoHide
onEntered: { sourceComponent: PanelWindow {
if (autoHide && hidden) { id: peekWindow
showTimer.start()
}
}
onExited: { screen: modelData
if (autoHide && !hidden && !dockHovered && !anyAppHovered) { anchors.bottom: true
hideTimer.restart() anchors.left: true
} anchors.right: true
} focusable: false
} color: Color.transparent
Rectangle { WlrLayershell.namespace: "noctalia-dock-peek"
id: dockContainer WlrLayershell.exclusionMode: ExclusionMode.Auto // Always exclusive
width: dockLayout.implicitWidth + Style.marginL * scaling * 2
height: Math.round(iconSize * 1.6)
color: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity)
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: floatingMargin + (barAtBottom ? dockSpacing : 0)
radius: Style.radiusL * scaling
border.width: Math.max(1, Style.borderS * scaling)
border.color: Color.mOutline
// Fade and zoom animation properties implicitHeight: peekHeight
opacity: hidden ? 0 : 1
scale: hidden ? 0.85 : 1
Behavior on opacity { Rectangle {
NumberAnimation { anchors.fill: parent
duration: hidden ? hideAnimationDuration : showAnimationDuration color: barAtBottom ? Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) : Color.transparent
easing.type: Easing.InOutQuad
}
}
Behavior on scale {
NumberAnimation {
duration: hidden ? hideAnimationDuration : showAnimationDuration
easing.type: hidden ? Easing.InQuad : Easing.OutBack
easing.overshoot: hidden ? 0 : 1.05
}
} }
MouseArea { MouseArea {
id: dockMouseArea id: peekArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onEntered: { onEntered: {
dockHovered = true peekHovered = true
if (autoHide) { if (hidden) {
showTimer.stop() showTimer.start()
hideTimer.stop()
if (hidden) {
hidden = false
}
} }
} }
onExited: { onExited: {
dockHovered = false peekHovered = false
// Only start hide timer if we're not hovering over any app or the peek area if (!hidden && !dockHovered && !anyAppHovered) {
if (autoHide && !anyAppHovered && !peekArea.containsMouse) {
hideTimer.restart() hideTimer.restart()
} }
} }
} }
}
}
// DOCK WINDOW
Loader {
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (ToplevelManager.toplevels.values.length > 0)
sourceComponent: PanelWindow {
id: dockWindow
screen: modelData
focusable: false
color: Color.transparent
WlrLayershell.namespace: "noctalia-dock-main"
WlrLayershell.exclusionMode: Settings.data.dock.exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore
// Size to fit the dock container exactly
implicitWidth: dockContainerWrapper.width
implicitHeight: dockContainerWrapper.height
// Position above the bar if it's at bottom
anchors.bottom: true
margins.bottom: {
switch (Settings.data.bar.position) {
case "bottom":
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling + floatingMargin : floatingMargin)
default:
return floatingMargin
}
}
// Rectangle {
// anchors.fill: parent
// color: "#000FF0"
// z: -1
// }
// Wrapper item for scale/opacity animations
Item { Item {
id: dock id: dockContainerWrapper
width: dockLayout.implicitWidth width: dockContainer.width
height: parent.height - (Style.marginM * 2 * scaling) height: dockContainer.height
anchors.centerIn: parent anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
function getAppIcon(toplevel: Toplevel): string { // Apply animations to this wrapper
if (!toplevel) opacity: hidden ? 0 : 1
return "" scale: hidden ? 0.85 : 1
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
Behavior on opacity {
NumberAnimation {
duration: hidden ? hideAnimationDuration : showAnimationDuration
easing.type: Easing.InOutQuad
}
} }
RowLayout { Behavior on scale {
id: dockLayout NumberAnimation {
spacing: Style.marginL * scaling duration: hidden ? hideAnimationDuration : showAnimationDuration
Layout.preferredHeight: parent.height easing.type: hidden ? Easing.InQuad : Easing.OutBack
easing.overshoot: hidden ? 0 : 1.05
}
}
Rectangle {
id: dockContainer
width: dockLayout.implicitWidth + Style.marginM * scaling * 2
height: Math.round(iconSize * 1.5)
color: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity)
anchors.centerIn: parent anchors.centerIn: parent
radius: Style.radiusL * scaling
border.width: Math.max(1, Style.borderS * scaling)
border.color: Qt.alpha(Color.mOutline, Settings.data.dock.backgroundOpacity)
Repeater { MouseArea {
model: ToplevelManager ? ToplevelManager.toplevels : null id: dockMouseArea
anchors.fill: parent
hoverEnabled: true
delegate: Item { onEntered: {
id: appButton dockHovered = true
Layout.preferredWidth: iconSize if (autoHide) {
Layout.preferredHeight: iconSize showTimer.stop()
Layout.alignment: Qt.AlignCenter hideTimer.stop()
unloadTimer.stop() // Cancel unload if hovering
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
property bool hovered: appMouseArea.containsMouse
property string appId: modelData ? modelData.appId : ""
property string appTitle: modelData ? modelData.title : ""
// Individual tooltip for this app
NTooltip {
id: appTooltip
target: appButton
positionAbove: true
visible: false
} }
}
// The icon with better quality settings onExited: {
Image { dockHovered = false
id: appIcon if (autoHide && !anyAppHovered && !peekHovered) {
width: iconSize hideTimer.restart()
height: iconSize }
anchors.centerIn: parent }
source: dock.getAppIcon(modelData) }
visible: source.toString() !== ""
sourceSize.width: iconSize * 2
sourceSize.height: iconSize * 2
smooth: true
mipmap: true
antialiasing: true
fillMode: Image.PreserveAspectFit
cache: true
scale: appButton.hovered ? 1.15 : 1.0 Item {
id: dock
width: dockLayout.implicitWidth
height: parent.height - (Style.marginM * 2 * scaling)
anchors.centerIn: parent
Behavior on scale { function getAppIcon(toplevel: Toplevel): string {
NumberAnimation { if (!toplevel)
duration: Style.animationNormal return ""
easing.type: Easing.OutBack return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
easing.overshoot: 1.2 }
RowLayout {
id: dockLayout
spacing: Style.marginM * scaling
Layout.preferredHeight: parent.height
anchors.centerIn: parent
Repeater {
model: ToplevelManager ? ToplevelManager.toplevels : null
delegate: Item {
id: appButton
Layout.preferredWidth: iconSize
Layout.preferredHeight: iconSize
Layout.alignment: Qt.AlignCenter
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
property bool hovered: appMouseArea.containsMouse
property string appId: modelData ? modelData.appId : ""
property string appTitle: modelData ? modelData.title : ""
// Individual tooltip for this app
NTooltip {
id: appTooltip
target: appButton
positionAbove: true
visible: false
} }
}
}
// Fall back if no icon Image {
NIcon { id: appIcon
anchors.centerIn: parent width: iconSize
visible: !appIcon.visible height: iconSize
icon: "question-mark" anchors.centerIn: parent
font.pointSize: iconSize * 0.7 source: dock.getAppIcon(modelData)
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant visible: source.toString() !== ""
scale: appButton.hovered ? 1.15 : 1.0 sourceSize.width: iconSize * 2
sourceSize.height: iconSize * 2
smooth: true
mipmap: true
antialiasing: true
fillMode: Image.PreserveAspectFit
cache: true
Behavior on scale { scale: appButton.hovered ? 1.15 : 1.0
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.OutBack
easing.overshoot: 1.2
}
}
}
MouseArea { Behavior on scale {
id: appMouseArea NumberAnimation {
anchors.fill: parent duration: Style.animationNormal
hoverEnabled: true easing.type: Easing.OutBack
cursorShape: Qt.PointingHandCursor easing.overshoot: 1.2
acceptedButtons: Qt.LeftButton | Qt.MiddleButton }
onEntered: {
anyAppHovered = true
const appName = appButton.appTitle || appButton.appId || "Unknown"
appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
appTooltip.isVisible = true
if (autoHide) {
showTimer.stop()
hideTimer.stop()
if (hidden) {
hidden = false
} }
} }
}
onExited: { // Fall back if no icon
anyAppHovered = false NIcon {
appTooltip.hide() anchors.centerIn: parent
// Only start hide timer if we're not hovering over the dock or peek area visible: !appIcon.visible
if (autoHide && !dockHovered && !peekArea.containsMouse) { icon: "question-mark"
hideTimer.restart() font.pointSize: iconSize * 0.7
} color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
} scale: appButton.hovered ? 1.15 : 1.0
onClicked: function (mouse) { Behavior on scale {
if (mouse.button === Qt.MiddleButton && modelData?.close) { NumberAnimation {
modelData.close() duration: Style.animationFast
easing.type: Easing.OutBack
easing.overshoot: 1.2
}
}
} }
if (mouse.button === Qt.LeftButton && modelData?.activate) {
modelData.activate()
}
}
}
// Active indicator MouseArea {
Rectangle { id: appMouseArea
visible: isActive anchors.fill: parent
width: iconSize * 0.2 hoverEnabled: true
height: iconSize * 0.1 cursorShape: Qt.PointingHandCursor
color: Color.mPrimary acceptedButtons: Qt.LeftButton | Qt.MiddleButton
radius: Style.radiusXS * scaling
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: Style.marginXXS * 1.5 * scaling
// Pulse animation for active indicator onEntered: {
SequentialAnimation on opacity { anyAppHovered = true
running: isActive const appName = appButton.appTitle || appButton.appId || "Unknown"
loops: Animation.Infinite appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
NumberAnimation { appTooltip.isVisible = true
to: 0.6 if (autoHide) {
duration: Style.animationSlowest showTimer.stop()
easing.type: Easing.InOutQuad hideTimer.stop()
unloadTimer.stop() // Cancel unload if hovering app
}
}
onExited: {
anyAppHovered = false
appTooltip.hide()
if (autoHide && !dockHovered && !peekHovered) {
hideTimer.restart()
}
}
onClicked: function (mouse) {
if (mouse.button === Qt.MiddleButton && modelData?.close) {
modelData.close()
}
if (mouse.button === Qt.LeftButton && modelData?.activate) {
modelData.activate()
}
}
} }
NumberAnimation {
to: 1.0 // Active indicator
duration: Style.animationSlowest Rectangle {
easing.type: Easing.InOutQuad visible: isActive
width: iconSize * 0.2
height: iconSize * 0.1
color: Color.mPrimary
radius: Style.radiusXS * scaling
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
// Pulse animation for active indicator
SequentialAnimation on opacity {
running: isActive
loops: Animation.Infinite
NumberAnimation {
to: 0.6
duration: Style.animationSlowest
easing.type: Easing.InOutQuad
}
NumberAnimation {
to: 1.0
duration: Style.animationSlowest
easing.type: Easing.InOutQuad
}
}
} }
} }
} }

View file

@ -8,17 +8,6 @@ import qs.Services
Item { Item {
id: root id: root
// Using Wayland protocols to get focused window then determine which screen it's on.
function getActiveScreen() {
const activeWindow = ToplevelManager.activeToplevel
if (activeWindow && activeWindow.screens.length > 0) {
return activeWindow.screens[0]
}
// Fall back to the primary screen
return Quickshell.screens[0]
}
IpcHandler { IpcHandler {
target: "screenRecorder" target: "screenRecorder"
function toggle() { function toggle() {
@ -31,14 +20,14 @@ Item {
IpcHandler { IpcHandler {
target: "settings" target: "settings"
function toggle() { function toggle() {
settingsPanel.toggle(getActiveScreen()) settingsPanel.toggle()
} }
} }
IpcHandler { IpcHandler {
target: "notifications" target: "notifications"
function toggleHistory() { function toggleHistory() {
notificationHistoryPanel.toggle(getActiveScreen()) notificationHistoryPanel.toggle()
} }
function toggleDND() { function toggleDND() {
Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
@ -55,15 +44,15 @@ Item {
IpcHandler { IpcHandler {
target: "launcher" target: "launcher"
function toggle() { function toggle() {
launcherPanel.toggle(getActiveScreen()) launcherPanel.toggle()
} }
function clipboard() { function clipboard() {
launcherPanel.setSearchText(">clip ") launcherPanel.setSearchText(">clip ")
launcherPanel.toggle(getActiveScreen()) launcherPanel.toggle()
} }
function calculator() { function calculator() {
launcherPanel.setSearchText(">calc ") launcherPanel.setSearchText(">calc ")
launcherPanel.toggle(getActiveScreen()) launcherPanel.toggle()
} }
} }
@ -110,7 +99,7 @@ Item {
AudioService.decreaseVolume() AudioService.decreaseVolume()
} }
function muteOutput() { function muteOutput() {
AudioService.setMuted(!AudioService.muted) AudioService.setOutputMuted(!AudioService.muted)
} }
function muteInput() { function muteInput() {
if (AudioService.source?.ready && AudioService.source?.audio) { if (AudioService.source?.ready && AudioService.source?.audio) {
@ -122,14 +111,14 @@ Item {
IpcHandler { IpcHandler {
target: "powerPanel" target: "powerPanel"
function toggle() { function toggle() {
powerPanel.toggle(getActiveScreen()) powerPanel.toggle()
} }
} }
IpcHandler { IpcHandler {
target: "sidePanel" target: "sidePanel"
function toggle() { function toggle() {
sidePanel.toggle(getActiveScreen()) sidePanel.toggle()
} }
} }

View file

@ -11,16 +11,10 @@ NPanel {
id: root id: root
// Panel configuration // Panel configuration
panelWidth: { preferredWidth: 500
var w = Math.round(Math.max(screen?.width * 0.3, 500) * scaling) preferredWidthRatio: 0.3
w = Math.min(w, screen?.width - Style.marginL * 2) preferredHeight: 600
return w preferredHeightRatio: 0.5
}
panelHeight: {
var h = Math.round(Math.max(screen?.height * 0.5, 600) * scaling)
h = Math.min(h, screen?.height - Style.barHeight * scaling - Style.marginL * 2)
return h
}
panelKeyboardFocus: true panelKeyboardFocus: true
panelBackgroundColor: Qt.alpha(Color.mSurface, Settings.data.appLauncher.backgroundOpacity) panelBackgroundColor: Qt.alpha(Color.mSurface, Settings.data.appLauncher.backgroundOpacity)
@ -287,9 +281,12 @@ NPanel {
} }
// Results list // Results list
ListView { NListView {
id: resultsList id: resultsList
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
@ -306,10 +303,6 @@ NPanel {
} }
} }
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
delegate: Rectangle { delegate: Rectangle {
id: entry id: entry

View file

@ -37,8 +37,7 @@ Item {
if (!query || query.trim() === "") { if (!query || query.trim() === "") {
// Return all apps alphabetically // Return all apps alphabetically
return entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map( return entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(app => createResultEntry(app))
app => createResultEntry(app))
} }
// Use fuzzy search if available, fallback to simple search // Use fuzzy search if available, fallback to simple search
@ -57,8 +56,7 @@ Item {
const name = (app.name || "").toLowerCase() const name = (app.name || "").toLowerCase()
const comment = (app.comment || "").toLowerCase() const comment = (app.comment || "").toLowerCase()
const generic = (app.genericName || "").toLowerCase() const generic = (app.genericName || "").toLowerCase()
return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes( return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm)
searchTerm)
}).sort((a, b) => { }).sort((a, b) => {
// Prioritize name matches // Prioritize name matches
const aName = a.name.toLowerCase() const aName = a.name.toLowerCase()
@ -85,7 +83,10 @@ Item {
if (Settings.data.appLauncher.useApp2Unit && app.id) { if (Settings.data.appLauncher.useApp2Unit && app.id) {
Logger.log("ApplicationsPlugin", `Using app2unit for: ${app.id}`) Logger.log("ApplicationsPlugin", `Using app2unit for: ${app.id}`)
Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"]) if (app.runInTerminal)
Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"])
else
Quickshell.execDetached(["app2unit", "--"].concat(app.command))
} else if (app.execute) { } else if (app.execute) {
app.execute() app.execute()
} else if (app.exec) { } else if (app.exec) {

View file

@ -8,8 +8,7 @@ Item {
function handleCommand(query) { function handleCommand(query) {
// Handle >calc command or direct math expressions after > // Handle >calc command or direct math expressions after >
return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression( return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(query.substring(1)))
query.substring(1)))
} }
function commands() { function commands() {

View file

@ -12,6 +12,7 @@ Scope {
property bool unlockInProgress: false property bool unlockInProgress: false
property bool showFailure: false property bool showFailure: false
property string errorMessage: "" property string errorMessage: ""
property string infoMessage: ""
property bool pamAvailable: typeof PamContext !== "undefined" property bool pamAvailable: typeof PamContext !== "undefined"
onCurrentTextChanged: { onCurrentTextChanged: {
@ -28,12 +29,6 @@ Scope {
return return
} }
if (currentText === "") {
errorMessage = "Password required"
showFailure = true
return
}
root.unlockInProgress = true root.unlockInProgress = true
errorMessage = "" errorMessage = ""
showFailure = false showFailure = false
@ -48,11 +43,12 @@ Scope {
user: Quickshell.env("USER") user: Quickshell.env("USER")
onPamMessage: { onPamMessage: {
Logger.log("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", Logger.log("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired)
responseRequired)
if (messageIsError) { if (messageIsError) {
errorMessage = message errorMessage = message
} else {
infoMessage = message
} }
if (responseRequired) { if (responseRequired) {

View file

@ -62,8 +62,7 @@ Loader {
Item { Item {
id: keyboardLayout id: keyboardLayout
property string currentLayout: (typeof KeyboardLayoutService !== 'undefined' property string currentLayout: (typeof KeyboardLayoutService !== 'undefined' && KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
&& KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
} }
Image { Image {
@ -227,12 +226,10 @@ Loader {
Repeater { Repeater {
model: CavaService.values.length * 2 model: CavaService.values.length * 2
Rectangle { Rectangle {
property int mirroredValueIndex: index < CavaService.values.length ? index : (CavaService.values.length property int mirroredValueIndex: index < CavaService.values.length ? index : (CavaService.values.length * 2 - 1 - index)
* 2 - 1 - index)
property real mirroredAngle: (index / (CavaService.values.length * 2)) * 2 * Math.PI property real mirroredAngle: (index / (CavaService.values.length * 2)) * 2 * Math.PI
property real mirroredRadius: 70 * scaling property real mirroredRadius: 70 * scaling
property real mirroredBarLength: Math.max( property real mirroredBarLength: Math.max(2, CavaService.values[mirroredValueIndex] * 30 * scaling)
2, CavaService.values[mirroredValueIndex] * 30 * scaling)
property real mirroredBarWidth: 3 * scaling property real mirroredBarWidth: 3 * scaling
width: mirroredBarWidth width: mirroredBarWidth
height: mirroredBarLength height: mirroredBarLength
@ -428,8 +425,7 @@ Loader {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: batteryIndicator.batteryVisible visible: batteryIndicator.batteryVisible
NIcon { NIcon {
icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging, icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging, batteryIndicator.isReady)
batteryIndicator.isReady)
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
rotation: -90 rotation: -90
@ -515,6 +511,7 @@ Loader {
width: 0 width: 0
height: 0 height: 0
visible: false visible: false
enabled: !lockContext.unlockInProgress
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
color: Color.mOnSurface color: Color.mOnSurface
@ -544,7 +541,7 @@ Loader {
color: Color.mOnSurface color: Color.mOnSurface
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
visible: passwordInput.activeFocus visible: passwordInput.activeFocus && !lockContext.unlockInProgress
SequentialAnimation { SequentialAnimation {
id: typingEffect id: typingEffect
@ -588,7 +585,7 @@ Loader {
NText { NText {
text: { text: {
if (lockContext.unlockInProgress) if (lockContext.unlockInProgress)
return "Authenticating..." return lockContext.infoMessage || "Authenticating..."
if (lockContext.showFailure && lockContext.errorMessage) if (lockContext.showFailure && lockContext.errorMessage)
return lockContext.errorMessage return lockContext.errorMessage
if (lockContext.showFailure) if (lockContext.showFailure)
@ -750,7 +747,7 @@ Loader {
id: shutdownTooltipText id: shutdownTooltipText
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginM * scaling
anchors.fill: parent anchors.fill: parent
text: "Shut down the computer." text: "Shut down."
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -801,7 +798,7 @@ Loader {
id: restartTooltipText id: restartTooltipText
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginM * scaling
anchors.fill: parent anchors.fill: parent
text: "Restart the computer." text: "Restart."
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -853,7 +850,7 @@ Loader {
id: suspendTooltipText id: suspendTooltipText
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginM * scaling
anchors.fill: parent anchors.fill: parent
text: "Suspend the system." text: "Suspend."
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View file

@ -25,10 +25,7 @@ Variants {
property var removingNotifications: ({}) property var removingNotifications: ({})
// If no notification display activated in settings, then show them all // If no notification display activated in settings, then show them all
active: Settings.isLoaded && modelData active: Settings.isLoaded && modelData && (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(modelData.name) || (Settings.data.notifications.monitors.length === 0)) : false
&& (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(
modelData.name)
|| (Settings.data.notifications.monitors.length === 0)) : false
visible: (NotificationService.notificationModel.count > 0) visible: (NotificationService.notificationModel.count > 0)
@ -36,13 +33,50 @@ Variants {
screen: modelData screen: modelData
color: Color.transparent color: Color.transparent
// Position based on bar location // Position based on bar location - always at top
anchors.top: Settings.data.bar.position === "top" anchors.top: true
anchors.bottom: Settings.data.bar.position === "bottom" anchors.right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
anchors.right: true anchors.left: Settings.data.bar.position === "left"
margins.top: Settings.data.bar.position === "top" ? (Style.barHeight + Style.marginM) * scaling : 0
margins.bottom: Settings.data.bar.position === "bottom" ? (Style.barHeight + Style.marginM) * scaling : 0 margins.top: {
margins.right: Style.marginM * scaling switch (Settings.data.bar.position) {
case "top":
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
default:
return Style.marginM * scaling
}
}
margins.bottom: {
switch (Settings.data.bar.position) {
case "bottom":
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
default:
return 0
}
}
margins.left: {
switch (Settings.data.bar.position) {
case "left":
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
default:
return 0
}
}
margins.right: {
switch (Settings.data.bar.position) {
case "right":
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
case "top":
case "bottom":
return Style.marginM * scaling
default:
return 0
}
}
implicitWidth: 360 * scaling implicitWidth: 360 * scaling
implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling) implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling)
//WlrLayershell.layer: WlrLayer.Overlay //WlrLayershell.layer: WlrLayer.Overlay
@ -80,10 +114,10 @@ Variants {
// Main notification container // Main notification container
ColumnLayout { ColumnLayout {
id: notificationStack id: notificationStack
// Position based on bar location // Position based on bar location - always at top
anchors.top: Settings.data.bar.position === "top" ? parent.top : undefined anchors.top: parent.top
anchors.bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined anchors.right: (Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom") ? parent.right : undefined
anchors.right: parent.right anchors.left: Settings.data.bar.position === "left" ? parent.left : undefined
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
width: 360 * scaling width: 360 * scaling
visible: true visible: true
@ -181,8 +215,7 @@ Variants {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NText { NText {
text: `${(model.appName || model.desktopEntry) text: `${(model.appName || model.desktopEntry) || "Unknown App"} · ${NotificationService.formatTimestamp(model.timestamp)}`
|| "Unknown App"} · ${NotificationService.formatTimestamp(model.timestamp)}`
color: Color.mSecondary color: Color.mSecondary
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
} }
@ -249,8 +282,7 @@ Variants {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: model.rawNotification && model.rawNotification.actions visible: model.rawNotification && model.rawNotification.actions && model.rawNotification.actions.length > 0
&& model.rawNotification.actions.length > 0
property var notificationActions: model.rawNotification ? model.rawNotification.actions : [] property var notificationActions: model.rawNotification ? model.rawNotification.actions : []
@ -293,7 +325,7 @@ Variants {
// Close button positioned absolutely // Close button positioned absolutely
NIconButton { NIconButton {
icon: "close" icon: "close"
tooltipText: "Close" tooltipText: "Close."
sizeRatio: 0.6 sizeRatio: 0.6
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Style.marginM * scaling anchors.topMargin: Style.marginM * scaling

View file

@ -12,9 +12,10 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
panelWidth: 380 * scaling preferredWidth: 380
panelHeight: 500 * scaling preferredHeight: 500
panelAnchorRight: true panelAnchorRight: Settings.data.bar.position === "right"
panelKeyboardFocus: true
panelContent: Rectangle { panelContent: Rectangle {
id: notificationRect id: notificationRect
@ -56,12 +57,15 @@ NPanel {
icon: "trash" icon: "trash"
tooltipText: "Clear history" tooltipText: "Clear history"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: NotificationService.clearHistory() onClicked: {
NotificationService.clearHistory()
root.close()
}
} }
NIconButton { NIconButton {
icon: "close" icon: "close"
tooltipText: "Close" tooltipText: "Close."
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: {
root.close() root.close()
@ -115,10 +119,13 @@ NPanel {
} }
// Notification list // Notification list
ListView { NListView {
id: notificationList id: notificationList
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
model: NotificationService.historyModel model: NotificationService.historyModel
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
clip: true clip: true
@ -129,7 +136,7 @@ NPanel {
width: notificationList.width width: notificationList.width
height: notificationLayout.implicitHeight + (Style.marginM * scaling * 2) height: notificationLayout.implicitHeight + (Style.marginM * scaling * 2)
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: notificationMouseArea.containsMouse ? Color.mSecondary : Color.mSurfaceVariant color: notificationMouseArea.containsMouse ? Color.mTertiary : Color.mSurfaceVariant
border.color: Qt.alpha(Color.mOutline, Style.opacityMedium) border.color: Qt.alpha(Color.mOutline, Style.opacityMedium)
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
@ -145,13 +152,7 @@ NPanel {
Layout.preferredHeight: 28 * scaling Layout.preferredHeight: 28 * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
// Prefer stable themed icons over transient image paths // Prefer stable themed icons over transient image paths
imagePath: (appIcon imagePath: (appIcon && appIcon !== "") ? (AppIcons.iconFromName(appIcon, "application-x-executable") || appIcon) : ((AppIcons.iconForAppId(desktopEntry || appName, "application-x-executable") || (image && image !== "" ? image : AppIcons.iconFromName("application-x-executable", "application-x-executable"))))
&& appIcon !== "") ? (AppIcons.iconFromName(appIcon, "application-x-executable")
|| appIcon) : ((AppIcons.iconForAppId(desktopEntry
|| appName, "application-x-executable")
|| (image && image
!== "" ? image : AppIcons.iconFromName("application-x-executable",
"application-x-executable"))))
borderColor: Color.transparent borderColor: Color.transparent
borderWidth: 0 borderWidth: 0
visible: true visible: true
@ -168,7 +169,7 @@ NPanel {
text: (summary || "No summary").substring(0, 100) text: (summary || "No summary").substring(0, 100)
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
font.weight: Font.Medium font.weight: Font.Medium
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mPrimary color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mPrimary
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.fillWidth: true Layout.fillWidth: true
maximumLineCount: 2 maximumLineCount: 2
@ -178,7 +179,7 @@ NPanel {
NText { NText {
text: (body || "").substring(0, 150) text: (body || "").substring(0, 150)
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.fillWidth: true Layout.fillWidth: true
maximumLineCount: 3 maximumLineCount: 3
@ -189,7 +190,7 @@ NPanel {
NText { NText {
text: NotificationService.formatTimestamp(timestamp) text: NotificationService.formatTimestamp(timestamp)
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
Layout.fillWidth: true Layout.fillWidth: true
} }
} }

View file

@ -13,8 +13,8 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
panelWidth: 440 * scaling preferredWidth: 440
panelHeight: 380 * scaling preferredHeight: 410
panelAnchorHorizontalCenter: true panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true panelAnchorVerticalCenter: true
panelKeyboardFocus: true panelKeyboardFocus: true
@ -224,6 +224,7 @@ NPanel {
root.close() root.close()
} }
} }
context: Qt.WidgetShortcut
enabled: root.opened enabled: root.opened
} }
@ -262,8 +263,7 @@ NPanel {
Layout.preferredHeight: Style.baseWidgetSize * 0.8 * scaling Layout.preferredHeight: Style.baseWidgetSize * 0.8 * scaling
NText { NText {
text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil( text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil(timeRemaining / 1000)} seconds...` : "Power Menu"
timeRemaining / 1000)} seconds...` : "Power Options"
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
color: timerActive ? Color.mPrimary : Color.mOnSurface color: timerActive ? Color.mPrimary : Color.mOnSurface
@ -292,6 +292,10 @@ NPanel {
} }
} }
NDivider {
Layout.fillWidth: true
}
// Power options // Power options
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
@ -337,7 +341,7 @@ NPanel {
return Qt.alpha(Color.mPrimary, 0.08) return Qt.alpha(Color.mPrimary, 0.08)
} }
if (isSelected || mouseArea.containsMouse) { if (isSelected || mouseArea.containsMouse) {
return Color.mSecondary return Color.mTertiary
} }
return Color.transparent return Color.transparent
} }
@ -367,7 +371,7 @@ NPanel {
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse) if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
return Color.mError return Color.mError
if (buttonRoot.isSelected || mouseArea.containsMouse) if (buttonRoot.isSelected || mouseArea.containsMouse)
return Color.mOnSecondary return Color.mOnTertiary
return Color.mOnSurface return Color.mOnSurface
} }
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
@ -401,7 +405,7 @@ NPanel {
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse) if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
return Color.mError return Color.mError
if (buttonRoot.isSelected || mouseArea.containsMouse) if (buttonRoot.isSelected || mouseArea.containsMouse)
return Color.mOnSecondary return Color.mOnTertiary
return Color.mOnSurface return Color.mOnSurface
} }
@ -426,7 +430,7 @@ NPanel {
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse) if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
return Color.mError return Color.mError
if (buttonRoot.isSelected || mouseArea.containsMouse) if (buttonRoot.isSelected || mouseArea.containsMouse)
return Color.mOnSecondary return Color.mOnTertiary
return Color.mOnSurfaceVariant return Color.mOnSurfaceVariant
} }
opacity: Style.opacityHeavy opacity: Style.opacityHeavy

View file

@ -19,10 +19,8 @@ NBox {
signal reorderWidget(string section, int fromIndex, int toIndex) signal reorderWidget(string section, int fromIndex, int toIndex)
signal updateWidgetSettings(string section, int index, var settings) signal updateWidgetSettings(string section, int index, var settings)
signal dragPotentialStarted signal dragPotentialStarted
// Emitted when a widget is pressed (potential drag start)
signal dragPotentialEnded signal dragPotentialEnded
// Emitted when interaction ends (drag or click)
color: Color.mSurface color: Color.mSurface
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: { Layout.minimumHeight: {
@ -43,17 +41,19 @@ NBox {
const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => { const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => {
return acc + character.charCodeAt(0) return acc + character.charCodeAt(0)
}, 0) }, 0)
switch (totalSum % 5) { switch (totalSum % 6) {
case 0: case 0:
return Color.mPrimary return [Color.mPrimary, Color.mOnPrimary]
case 1: case 1:
return Color.mSecondary return [Color.mSecondary, Color.mOnSecondary]
case 2: case 2:
return Color.mTertiary return [Color.mTertiary, Color.mOnTertiary]
case 3: case 3:
return Color.mError return [Color.mError, Color.mOnError]
case 4: case 4:
return Color.mOnSurface return [Color.mOnSurface, Color.mSurface]
case 5:
return [Color.mOnSurfaceVariant, Color.mSurfaceVariant]
} }
} }
@ -131,7 +131,7 @@ NBox {
width: widgetContent.implicitWidth + Style.marginL * scaling width: widgetContent.implicitWidth + Style.marginL * scaling
height: Style.baseWidgetSize * 1.15 * scaling height: Style.baseWidgetSize * 1.15 * scaling
radius: Style.radiusL * scaling radius: Style.radiusL * scaling
color: root.getWidgetColor(modelData) color: root.getWidgetColor(modelData)[0]
border.color: Color.mOutline border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
@ -147,12 +147,12 @@ NBox {
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: 150 duration: Style.animationFast
} }
} }
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: 150 duration: Style.animationFast
} }
} }
@ -164,7 +164,7 @@ NBox {
NText { NText {
text: modelData.id text: modelData.id
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
color: Color.mOnPrimary color: root.getWidgetColor(modelData)[1]
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight elide: Text.ElideRight
Layout.preferredWidth: 80 * scaling Layout.preferredWidth: 80 * scaling
@ -240,7 +240,7 @@ NBox {
width: 0 width: 0
height: Style.baseWidgetSize * 1.15 * scaling height: Style.baseWidgetSize * 1.15 * scaling
radius: Style.radiusL * scaling radius: Style.radiusL * scaling
color: "transparent" color: Color.transparent
border.color: Color.mOutline border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
opacity: 0.7 opacity: 0.7
@ -305,7 +305,7 @@ NBox {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
preventStealing: false preventStealing: false
propagateComposedEvents: !dragStarted propagateComposedEvents: false
hoverEnabled: true // Always track mouse for drag operations hoverEnabled: true // Always track mouse for drag operations
property point startPos: Qt.point(0, 0) property point startPos: Qt.point(0, 0)
@ -339,12 +339,10 @@ NBox {
continue continue
// Check distance to left edge (insert before) // Check distance to left edge (insert before)
const leftDist = Math.sqrt(Math.pow(mouseX - widget.x, const leftDist = Math.sqrt(Math.pow(mouseX - widget.x, 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
// Check distance to right edge (insert after) // Check distance to right edge (insert after)
const rightDist = Math.sqrt(Math.pow(mouseX - (widget.x + widget.width), const rightDist = Math.sqrt(Math.pow(mouseX - (widget.x + widget.width), 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
if (leftDist < minDistance) { if (leftDist < minDistance) {
minDistance = leftDist minDistance = leftDist
@ -355,8 +353,7 @@ NBox {
if (rightDist < minDistance) { if (rightDist < minDistance) {
minDistance = rightDist minDistance = rightDist
bestIndex = i + 1 bestIndex = i + 1
bestPosition = Qt.point(widget.x + widget.width + Style.marginXS * scaling - dropIndicator.width / 2, bestPosition = Qt.point(widget.x + widget.width + Style.marginXS * scaling - dropIndicator.width / 2, widget.y)
widget.y)
} }
} }
@ -368,8 +365,7 @@ NBox {
if (dist < minDistance && mouseX < firstWidget.x + firstWidget.width / 2) { if (dist < minDistance && mouseX < firstWidget.x + firstWidget.width / 2) {
minDistance = dist minDistance = dist
bestIndex = 0 bestIndex = 0
bestPosition = Qt.point(Math.max(0, firstWidget.x - dropIndicator.width - Style.marginS * scaling), bestPosition = Qt.point(Math.max(0, firstWidget.x - dropIndicator.width - Style.marginS * scaling), firstWidget.y)
firstWidget.y)
} }
} }
} }
@ -419,8 +415,7 @@ NBox {
for (var i = 0; i < widgetModel.length; i++) { for (var i = 0; i < widgetModel.length; i++) {
const widget = widgetFlow.children[i] const widget = widgetFlow.children[i]
if (widget && widget.widgetIndex !== undefined) { if (widget && widget.widgetIndex !== undefined) {
if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && mouse.y >= widget.y if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && mouse.y >= widget.y && mouse.y <= widget.y + widget.height) {
&& mouse.y <= widget.y + widget.height) {
const localX = mouse.x - widget.x const localX = mouse.x - widget.x
const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth) const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth)
@ -458,7 +453,7 @@ NBox {
// Setup ghost widget // Setup ghost widget
if (draggedWidget) { if (draggedWidget) {
dragGhost.width = draggedWidget.width dragGhost.width = draggedWidget.width
dragGhost.color = root.getWidgetColor(draggedModelData) dragGhost.color = root.getWidgetColor(draggedModelData)[0]
ghostText.text = draggedModelData.id ghostText.text = draggedModelData.id
} }
} }

View file

@ -19,7 +19,7 @@ Popup {
x: (parent.width - width) * 0.5 x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5 y: (parent.height - height) * 0.5
width: 420 * scaling width: 500 * scaling
height: content.implicitHeight + padding * 2 height: content.implicitHeight + padding * 2
padding: Style.marginXL * scaling padding: Style.marginXL * scaling
modal: true modal: true
@ -46,6 +46,7 @@ Popup {
"Brightness": "WidgetSettings/BrightnessSettings.qml", "Brightness": "WidgetSettings/BrightnessSettings.qml",
"Clock": "WidgetSettings/ClockSettings.qml", "Clock": "WidgetSettings/ClockSettings.qml",
"CustomButton": "WidgetSettings/CustomButtonSettings.qml", "CustomButton": "WidgetSettings/CustomButtonSettings.qml",
"KeyboardLayout": "WidgetSettings/KeyboardLayoutSettings.qml",
"MediaMini": "WidgetSettings/MediaMiniSettings.qml", "MediaMini": "WidgetSettings/MediaMiniSettings.qml",
"Microphone": "WidgetSettings/MicrophoneSettings.qml", "Microphone": "WidgetSettings/MicrophoneSettings.qml",
"NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml", "NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml",

View file

@ -14,22 +14,36 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
property int valueWarningThreshold: widgetData.warningThreshold
!== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage settings.displayMode = valueDisplayMode
settings.warningThreshold = valueWarningThreshold settings.warningThreshold = valueWarningThreshold
return settings return settings
} }
NToggle { NComboBox {
label: "Always show percentage" label: "Display mode"
checked: root.valueAlwaysShowPercentage description: "Choose how you'd like this value to appear."
onToggled: checked => root.valueAlwaysShowPercentage = checked minimumWidth: 134 * scaling
model: ListModel {
ListElement {
key: "onhover"
name: "On Hover"
}
ListElement {
key: "alwaysShow"
name: "Always Show"
}
ListElement {
key: "alwaysHide"
name: "Always Hide"
}
}
currentKey: root.valueDisplayMode
onSelected: key => root.valueDisplayMode = key
} }
NSpinBox { NSpinBox {

View file

@ -14,18 +14,33 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage settings.displayMode = valueDisplayMode
return settings return settings
} }
NToggle { NComboBox {
label: "Always show percentage" label: "Display mode"
checked: valueAlwaysShowPercentage description: "Choose how you'd like this value to appear."
onToggled: checked => valueAlwaysShowPercentage = checked minimumWidth: 134 * scaling
model: ListModel {
ListElement {
key: "onhover"
name: "On Hover"
}
ListElement {
key: "alwaysShow"
name: "Always Show"
}
ListElement {
key: "alwaysHide"
name: "Always Hide"
}
}
currentKey: valueDisplayMode
onSelected: key => valueDisplayMode = key
} }
} }

View file

@ -14,24 +14,41 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property bool valueShowDate: widgetData.showDate !== undefined ? widgetData.showDate : widgetMetadata.showDate property string valueDisplayFormat: widgetData.displayFormat !== undefined ? widgetData.displayFormat : widgetMetadata.displayFormat
property bool valueUse12h: widgetData.use12HourClock !== undefined ? widgetData.use12HourClock : widgetMetadata.use12HourClock property bool valueUse12h: widgetData.use12HourClock !== undefined ? widgetData.use12HourClock : widgetMetadata.use12HourClock
property bool valueShowSeconds: widgetData.showSeconds !== undefined ? widgetData.showSeconds : widgetMetadata.showSeconds
property bool valueReverseDayMonth: widgetData.reverseDayMonth !== undefined ? widgetData.reverseDayMonth : widgetMetadata.reverseDayMonth property bool valueReverseDayMonth: widgetData.reverseDayMonth !== undefined ? widgetData.reverseDayMonth : widgetMetadata.reverseDayMonth
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.showDate = valueShowDate settings.displayFormat = valueDisplayFormat
settings.use12HourClock = valueUse12h settings.use12HourClock = valueUse12h
settings.showSeconds = valueShowSeconds
settings.reverseDayMonth = valueReverseDayMonth settings.reverseDayMonth = valueReverseDayMonth
return settings return settings
} }
NToggle { NComboBox {
label: "Show date" label: "Display Format"
checked: valueShowDate model: ListModel {
onToggled: checked => valueShowDate = checked ListElement {
key: "time"
name: "HH:mm"
}
ListElement {
key: "time-seconds"
name: "HH:mm:ss"
}
ListElement {
key: "time-date"
name: "HH:mm - Date"
}
ListElement {
key: "time-date-short"
name: "HH:mm - Short Date"
}
}
currentKey: valueDisplayFormat
onSelected: key => valueDisplayFormat = key
minimumWidth: 230 * scaling
} }
NToggle { NToggle {
@ -40,12 +57,6 @@ ColumnLayout {
onToggled: checked => valueUse12h = checked onToggled: checked => valueUse12h = checked
} }
NToggle {
label: "Show seconds"
checked: valueShowSeconds
onToggled: checked => valueShowSeconds = checked
}
NToggle { NToggle {
label: "Reverse day and month" label: "Reverse day and month"
checked: valueReverseDayMonth checked: valueReverseDayMonth

View file

@ -19,6 +19,8 @@ ColumnLayout {
settings.leftClickExec = leftClickExecInput.text settings.leftClickExec = leftClickExecInput.text
settings.rightClickExec = rightClickExecInput.text settings.rightClickExec = rightClickExecInput.text
settings.middleClickExec = middleClickExecInput.text settings.middleClickExec = middleClickExecInput.text
settings.textCommand = textCommandInput.text
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10)
return settings return settings
} }
@ -48,18 +50,16 @@ ColumnLayout {
Popup { Popup {
id: iconPicker id: iconPicker
modal: true modal: true
property real panelWidth: { width: {
var w = Math.round(Math.max(Screen.width * 0.35, 900) * scaling) var w = Math.round(Math.max(Screen.width * 0.35, 900) * scaling)
w = Math.min(w, Screen.width - Style.marginL * 2) w = Math.min(w, Screen.width - Style.marginL * 2)
return w return w
} }
property real panelHeight: { height: {
var h = Math.round(Math.max(Screen.height * 0.65, 700) * scaling) var h = Math.round(Math.max(Screen.height * 0.65, 700) * scaling)
h = Math.min(h, Screen.height - Style.barHeight * scaling - Style.marginL * 2) h = Math.min(h, Screen.height - Style.barHeight * scaling - Style.marginL * 2)
return h return h
} }
width: panelWidth
height: panelHeight
anchors.centerIn: Overlay.overlay anchors.centerIn: Overlay.overlay
padding: Style.marginXL * scaling padding: Style.marginXL * scaling
@ -117,10 +117,12 @@ ColumnLayout {
} }
// Icon grid // Icon grid
ScrollView { NScrollView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AlwaysOn
GridView { GridView {
id: grid id: grid
@ -228,4 +230,33 @@ ColumnLayout {
placeholderText: "Enter command to execute (app or custom script)" placeholderText: "Enter command to execute (app or custom script)"
text: widgetData.middleClickExec || widgetMetadata.middleClickExec text: widgetData.middleClickExec || widgetMetadata.middleClickExec
} }
NDivider {
Layout.fillWidth: true
}
NText {
text: "Dynamic Text"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NTextInput {
id: textCommandInput
Layout.fillWidth: true
label: "Text Command"
description: "Shell command to run periodically (first line becomes the text)."
placeholderText: "echo \"Hello World\""
text: widgetData?.textCommand || widgetMetadata.textCommand
}
NTextInput {
id: textIntervalInput
Layout.fillWidth: true
label: "Refresh Interval"
description: "Interval in milliseconds."
placeholderText: String(widgetMetadata.textIntervalMs || 3000)
text: widgetData && widgetData.textIntervalMs !== undefined ? String(widgetData.textIntervalMs) : ""
}
} }

View file

@ -0,0 +1,46 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.displayMode = valueDisplayMode
return settings
}
NComboBox {
label: "Display mode"
description: "Choose how you'd like this value to appear."
minimumWidth: 134 * scaling
model: ListModel {
ListElement {
key: "onhover"
name: "On Hover"
}
ListElement {
key: "forceOpen"
name: "Force Open"
}
ListElement {
key: "alwaysHide"
name: "Always Hide"
}
}
currentKey: valueDisplayMode
onSelected: key => valueDisplayMode = key
}
}

View file

@ -14,18 +14,33 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage settings.displayMode = valueDisplayMode
return settings return settings
} }
NToggle { NComboBox {
label: "Always show percentage" label: "Display mode"
checked: valueAlwaysShowPercentage description: "Choose how you'd like this value to appear."
onToggled: checked => valueAlwaysShowPercentage = checked minimumWidth: 134 * scaling
model: ListModel {
ListElement {
key: "onhover"
name: "On Hover"
}
ListElement {
key: "alwaysShow"
name: "Always Show"
}
ListElement {
key: "alwaysHide"
name: "Always Hide"
}
}
currentKey: valueDisplayMode
onSelected: key => valueDisplayMode = key
} }
} }

View file

@ -16,20 +16,15 @@ ColumnLayout {
// Local, editable state for checkboxes // Local, editable state for checkboxes
property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage
property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp
property bool valueShowGpuTemp: widgetData.showGpuTemp !== undefined ? widgetData.showGpuTemp : (widgetMetadata.showGpuTemp
|| false)
property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage
property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
!== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent property bool valueShowNetworkStats: widgetData.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
property bool valueShowNetworkStats: widgetData.showNetworkStats
!== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.showCpuUsage = valueShowCpuUsage settings.showCpuUsage = valueShowCpuUsage
settings.showCpuTemp = valueShowCpuTemp settings.showCpuTemp = valueShowCpuTemp
settings.showGpuTemp = valueShowGpuTemp
settings.showMemoryUsage = valueShowMemoryUsage settings.showMemoryUsage = valueShowMemoryUsage
settings.showMemoryAsPercent = valueShowMemoryAsPercent settings.showMemoryAsPercent = valueShowMemoryAsPercent
settings.showNetworkStats = valueShowNetworkStats settings.showNetworkStats = valueShowNetworkStats
@ -53,14 +48,6 @@ ColumnLayout {
onToggled: checked => valueShowCpuTemp = checked onToggled: checked => valueShowCpuTemp = checked
} }
NToggle {
id: showGpuTemp
Layout.fillWidth: true
label: "GPU temperature"
checked: valueShowGpuTemp
onToggled: checked => valueShowGpuTemp = checked
}
NToggle { NToggle {
id: showMemoryUsage id: showMemoryUsage
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -14,18 +14,33 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage settings.displayMode = valueDisplayMode
return settings return settings
} }
NToggle { NComboBox {
label: "Always show percentage" label: "Display mode"
checked: valueAlwaysShowPercentage description: "Choose how you'd like this value to appear."
onToggled: checked => valueAlwaysShowPercentage = checked minimumWidth: 134 * scaling
model: ListModel {
ListElement {
key: "onhover"
name: "On Hover"
}
ListElement {
key: "alwaysShow"
name: "Always Show"
}
ListElement {
key: "alwaysHide"
name: "Always Hide"
}
}
currentKey: valueDisplayMode
onSelected: key => valueDisplayMode = key
} }
} }

View file

@ -16,6 +16,7 @@ ColumnLayout {
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.labelMode = labelModeCombo.currentKey settings.labelMode = labelModeCombo.currentKey
settings.hideUnoccupied = hideUnoccupiedToggle.checked
return settings return settings
} }
@ -41,4 +42,12 @@ ColumnLayout {
onSelected: key => labelModeCombo.currentKey = key onSelected: key => labelModeCombo.currentKey = key
minimumWidth: 200 * scaling minimumWidth: 200 * scaling
} }
NToggle {
id: hideUnoccupiedToggle
label: "Hide unoccupied"
description: "Don't display workspaces without windows."
checked: widgetData.hideUnoccupied
onToggled: checked => hideUnoccupiedToggle.checked = checked
}
} }

View file

@ -11,16 +11,11 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
panelWidth: { preferredWidth: 1000
var w = Math.round(Math.max(screen?.width * 0.4, 1000) * scaling) preferredHeight: 1000
w = Math.min(w, screen?.width - Style.marginL * 2) preferredWidthRatio: 0.4
return w preferredHeightRatio: 0.75
}
panelHeight: {
var h = Math.round(Math.max(screen?.height * 0.75, 800) * scaling)
h = Math.min(h, screen?.height - Style.barHeight * scaling - Style.marginL * 2)
return h
}
panelAnchorHorizontalCenter: true panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true panelAnchorVerticalCenter: true
@ -31,13 +26,14 @@ NPanel {
About, About,
Audio, Audio,
Bar, Bar,
Dock,
Hooks, Hooks,
Launcher, Launcher,
Brightness,
ColorScheme, ColorScheme,
Display, Display,
General, General,
Network, Network,
Notification,
ScreenRecorder, ScreenRecorder,
Weather, Weather,
Wallpaper, Wallpaper,
@ -72,15 +68,10 @@ NPanel {
id: barTab id: barTab
Tabs.BarTab {} Tabs.BarTab {}
} }
Component { Component {
id: audioTab id: audioTab
Tabs.AudioTab {} Tabs.AudioTab {}
} }
Component {
id: brightnessTab
Tabs.BrightnessTab {}
}
Component { Component {
id: displayTab id: displayTab
Tabs.DisplayTab {} Tabs.DisplayTab {}
@ -117,6 +108,14 @@ NPanel {
id: hooksTab id: hooksTab
Tabs.HooksTab {} Tabs.HooksTab {}
} }
Component {
id: dockTab
Tabs.DockTab {}
}
Component {
id: notificationTab
Tabs.NotificationTab {}
}
// Order *DOES* matter // Order *DOES* matter
function updateTabsModel() { function updateTabsModel() {
@ -130,6 +129,11 @@ NPanel {
"label": "Bar", "label": "Bar",
"icon": "settings-bar", "icon": "settings-bar",
"source": barTab "source": barTab
}, {
"id": SettingsPanel.Tab.Dock,
"label": "Dock",
"icon": "settings-dock",
"source": dockTab
}, { }, {
"id": SettingsPanel.Tab.Launcher, "id": SettingsPanel.Tab.Launcher,
"label": "Launcher", "label": "Launcher",
@ -145,16 +149,16 @@ NPanel {
"label": "Display", "label": "Display",
"icon": "settings-display", "icon": "settings-display",
"source": displayTab "source": displayTab
}, {
"id": SettingsPanel.Tab.Notification,
"label": "Notification",
"icon": "settings-notification",
"source": notificationTab
}, { }, {
"id": SettingsPanel.Tab.Network, "id": SettingsPanel.Tab.Network,
"label": "Network", "label": "Network",
"icon": "settings-network", "icon": "settings-network",
"source": networkTab "source": networkTab
}, {
"id": SettingsPanel.Tab.Brightness,
"label": "Brightness",
"icon": "settings-brightness",
"source": brightnessTab
}, { }, {
"id": SettingsPanel.Tab.Weather, "id": SettingsPanel.Tab.Weather,
"label": "Weather", "label": "Weather",
@ -223,8 +227,7 @@ NPanel {
if (activeScrollView && activeScrollView.ScrollBar.vertical) { if (activeScrollView && activeScrollView.ScrollBar.vertical) {
const scrollBar = activeScrollView.ScrollBar.vertical const scrollBar = activeScrollView.ScrollBar.vertical
const stepSize = activeScrollView.height * 0.1 // Scroll 10% of viewport const stepSize = activeScrollView.height * 0.1 // Scroll 10% of viewport
scrollBar.position = Math.min(scrollBar.position + stepSize / activeScrollView.contentHeight, scrollBar.position = Math.min(scrollBar.position + stepSize / activeScrollView.contentHeight, 1.0 - scrollBar.size)
1.0 - scrollBar.size)
} }
} }
@ -240,8 +243,7 @@ NPanel {
if (activeScrollView && activeScrollView.ScrollBar.vertical) { if (activeScrollView && activeScrollView.ScrollBar.vertical) {
const scrollBar = activeScrollView.ScrollBar.vertical const scrollBar = activeScrollView.ScrollBar.vertical
const pageSize = activeScrollView.height * 0.9 // Scroll 90% of viewport const pageSize = activeScrollView.height * 0.9 // Scroll 90% of viewport
scrollBar.position = Math.min(scrollBar.position + pageSize / activeScrollView.contentHeight, scrollBar.position = Math.min(scrollBar.position + pageSize / activeScrollView.contentHeight, 1.0 - scrollBar.size)
1.0 - scrollBar.size)
} }
} }
@ -466,7 +468,7 @@ NPanel {
NIcon { NIcon {
icon: root.tabsModel[currentTabIndex]?.icon icon: root.tabsModel[currentTabIndex]?.icon
color: Color.mPrimary color: Color.mPrimary
font.pointSize: Style.fontSizeXL * scaling font.pointSize: Style.fontSizeXXL * scaling
} }
// Main title // Main title
@ -482,7 +484,7 @@ NPanel {
// Close button // Close button
NIconButton { NIconButton {
icon: "close" icon: "close"
tooltipText: "Close" tooltipText: "Close."
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: root.close() onClicked: root.close()
} }
@ -522,11 +524,11 @@ NPanel {
anchors.fill: parent anchors.fill: parent
pressDelay: 200 pressDelay: 200
ScrollView { NScrollView {
id: scrollView id: scrollView
anchors.fill: parent anchors.fill: parent
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff horizontalPolicy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded verticalPolicy: ScrollBar.AsNeeded
padding: Style.marginL * scaling padding: Style.marginL * scaling
clip: true clip: true

View file

@ -10,107 +10,108 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: Style.marginL * scaling
property string latestVersion: GitHubService.latestVersion property string latestVersion: GitHubService.latestVersion
property string currentVersion: UpdateService.currentVersion property string currentVersion: UpdateService.currentVersion
property var contributors: GitHubService.contributors property var contributors: GitHubService.contributors
NText { NHeader {
text: "Noctalia Shell" label: "Noctalia Shell"
font.pointSize: Style.fontSizeXXXL * scaling description: "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell."
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.alignment: Qt.AlignCenter
Layout.bottomMargin: Style.marginS * scaling
} }
// Versions RowLayout {
GridLayout { spacing: Style.marginL * scaling
Layout.alignment: Qt.AlignCenter
columns: 2
rowSpacing: Style.marginXS * scaling
columnSpacing: Style.marginS * scaling
NText { // Versions
text: "Latest Version:" GridLayout {
color: Color.mOnSurface columns: 2
Layout.alignment: Qt.AlignRight rowSpacing: Style.marginXS * scaling
} columnSpacing: Style.marginS * scaling
NText { NText {
text: root.latestVersion text: "Latest Version:"
color: Color.mOnSurface color: Color.mOnSurface
font.weight: Style.fontWeightBold
}
NText {
text: "Installed Version:"
color: Color.mOnSurface
Layout.alignment: Qt.AlignRight
}
NText {
text: root.currentVersion
color: Color.mOnSurface
font.weight: Style.fontWeightBold
}
}
// Updater
Rectangle {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: Style.marginS * scaling
Layout.preferredWidth: Math.round(updateRow.implicitWidth + (Style.marginL * scaling * 2))
Layout.preferredHeight: Math.round(Style.barHeight * scaling)
radius: Style.radiusL * scaling
color: updateArea.containsMouse ? Color.mPrimary : Color.transparent
border.color: Color.mPrimary
border.width: Math.max(1, Style.borderS * scaling)
visible: {
if (root.latestVersion === "Unknown")
return false
const latest = root.latestVersion.replace("v", "").split(".")
const current = root.currentVersion.replace("v", "").split(".")
for (var i = 0; i < Math.max(latest.length, current.length); i++) {
const l = parseInt(latest[i] || "0")
const c = parseInt(current[i] || "0")
if (l > c)
return true
if (l < c)
return false
}
return false
}
RowLayout {
id: updateRow
anchors.centerIn: parent
spacing: Style.marginS * scaling
NIcon {
icon: "download"
font.pointSize: Style.fontSizeXXL * scaling
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
} }
NText { NText {
id: updateText text: root.latestVersion
text: "Download latest release" color: Color.mOnSurface
font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary }
NText {
text: "Installed Version:"
color: Color.mOnSurface
}
NText {
text: root.currentVersion
color: Color.mOnSurface
font.weight: Style.fontWeightBold
} }
} }
MouseArea { Item {
id: updateArea Layout.fillWidth: true
}
anchors.fill: parent // Update button
hoverEnabled: true Rectangle {
cursorShape: Qt.PointingHandCursor Layout.alignment: Qt.AlignRight
onClicked: { Layout.preferredWidth: Math.round(updateRow.implicitWidth + (Style.marginL * scaling * 2))
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"]) Layout.preferredHeight: Math.round(Style.barHeight * scaling)
radius: Style.radiusL * scaling
color: updateArea.containsMouse ? Color.mPrimary : Color.transparent
border.color: Color.mPrimary
border.width: Math.max(1, Style.borderS * scaling)
visible: {
if (root.latestVersion === "Unknown")
return false
const latest = root.latestVersion.replace("v", "").split(".")
const current = root.currentVersion.replace("v", "").split(".")
for (var i = 0; i < Math.max(latest.length, current.length); i++) {
const l = parseInt(latest[i] || "0")
const c = parseInt(current[i] || "0")
if (l > c)
return true
if (l < c)
return false
}
return false
}
RowLayout {
id: updateRow
anchors.centerIn: parent
spacing: Style.marginS * scaling
NIcon {
icon: "download"
font.pointSize: Style.fontSizeXXL * scaling
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
}
NText {
id: updateText
text: "Download latest release"
font.pointSize: Style.fontSizeL * scaling
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
}
}
MouseArea {
id: updateArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"])
}
} }
} }
} }
@ -121,17 +122,13 @@ ColumnLayout {
Layout.bottomMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling
} }
NText { NHeader {
text: `Shout-out to our ${root.contributors.length} <b>awesome</b> contributors!` label: "Contributors"
font.pointSize: Style.fontSizeL * scaling description: `Shout-out to our ${root.contributors.length} <b>awesome</b> contributors!`
color: Color.mOnSurface
Layout.alignment: Qt.AlignCenter
} }
GridView { GridView {
id: contributorsGrid id: contributorsGrid
Layout.topMargin: Style.marginL * scaling
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: cellWidth * 3 // Fixed 3 columns Layout.preferredWidth: cellWidth * 3 // Fixed 3 columns
Layout.preferredHeight: { Layout.preferredHeight: {
@ -192,7 +189,7 @@ ColumnLayout {
NText { NText {
text: modelData.login || "Unknown" text: modelData.login || "Unknown"
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface color: contributorArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
elide: Text.ElideRight elide: Text.ElideRight
Layout.fillWidth: true Layout.fillWidth: true
} }
@ -200,7 +197,7 @@ ColumnLayout {
NText { NText {
text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits") text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits")
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface color: contributorArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
} }
} }
} }

View file

@ -2,12 +2,18 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import qs.Widgets
import qs.Commons import qs.Commons
import qs.Services import qs.Services
import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: Style.marginL * scaling
NHeader {
label: "Volumes"
description: "Configure volume controls and audio levels."
}
property real localVolume: AudioService.volume property real localVolume: AudioService.volume
@ -20,7 +26,7 @@ ColumnLayout {
// Master Volume // Master Volume
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NLabel { NLabel {
@ -28,37 +34,29 @@ ColumnLayout {
description: "System-wide volume level." description: "System-wide volume level."
} }
RowLayout { // Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily
// Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily // Probably because they have some quick fades in and out to avoid clipping
// Probably because they have some quick fades in and out to avoid clipping // We use a timer to space out the updates, to avoid lock up
// We use a timer to space out the updates, to avoid lock up Timer {
Timer { interval: Style.animationFast
interval: Style.animationFast running: true
running: true repeat: true
repeat: true onTriggered: {
onTriggered: { if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
if (Math.abs(localVolume - AudioService.volume) >= 0.01) { AudioService.setVolume(localVolume)
AudioService.setVolume(localVolume)
}
} }
} }
}
NSlider { NValueSlider {
Layout.fillWidth: true Layout.fillWidth: true
from: 0 from: 0
to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0 to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0
value: localVolume value: localVolume
stepSize: 0.01 stepSize: 0.01
onMoved: { text: Math.floor(AudioService.volume * 100) + "%"
localVolume = value onMoved: {
} localVolume = value
}
NText {
text: Math.floor(AudioService.volume * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
} }
} }
} }
@ -67,7 +65,6 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
NToggle { NToggle {
label: "Mute Audio Output" label: "Mute Audio Output"
@ -83,33 +80,22 @@ ColumnLayout {
// Input Volume // Input Volume
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
NLabel { NLabel {
label: "Input Volume" label: "Input Volume"
description: "Microphone input volume level." description: "Microphone input volume level."
} }
RowLayout { NValueSlider {
NSlider { Layout.fillWidth: true
Layout.fillWidth: true from: 0
from: 0 to: 1.0
to: 1.0 value: AudioService.inputVolume
value: AudioService.inputVolume stepSize: 0.01
stepSize: 0.01 text: Math.floor(AudioService.inputVolume * 100) + "%"
onMoved: { onMoved: value => AudioService.setInputVolume(value)
AudioService.setInputVolume(value)
}
}
NText {
text: Math.floor(AudioService.inputVolume * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
} }
} }
@ -117,7 +103,6 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
NToggle { NToggle {
label: "Mute Audio Input" label: "Mute Audio Input"
@ -131,7 +116,6 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
NSpinBox { NSpinBox {
Layout.fillWidth: true Layout.fillWidth: true
@ -142,9 +126,7 @@ ColumnLayout {
value: Settings.data.audio.volumeStep value: Settings.data.audio.volumeStep
stepSize: 1 stepSize: 1
suffix: "%" suffix: "%"
onValueChanged: { onValueChanged: Settings.data.audio.volumeStep = value
Settings.data.audio.volumeStep = value
}
} }
} }
@ -158,12 +140,9 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NText { NHeader {
text: "Audio Devices" label: "Audio Devices"
font.pointSize: Style.fontSizeXXL * scaling description: "Configure audio input and output devices."
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// ------------------------------- // -------------------------------
@ -203,7 +182,6 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: Style.marginL * scaling
NLabel { NLabel {
label: "Input Device" label: "Input Device"
@ -234,12 +212,9 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
NText { NHeader {
text: "Media Player" label: "Media Player"
font.pointSize: Style.fontSizeXXL * scaling description: "Configure your favorite media players."
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// Preferred player // Preferred player
@ -360,12 +335,9 @@ ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Audio Visualizer" label: "Audio Visualizer"
font.pointSize: Style.fontSizeXXL * scaling description: "Customize visual effects that respond to audio playback."
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// AudioService Visualizer section // AudioService Visualizer section

View file

@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Commons import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@ -8,6 +9,20 @@ import qs.Modules.SettingsPanel.Bar
ColumnLayout { ColumnLayout {
id: root id: root
spacing: Style.marginL * scaling
// Helper functions to update arrays immutably
function addMonitor(list, name) {
const arr = (list || []).slice()
if (!arr.includes(name))
arr.push(name)
return arr
}
function removeMonitor(list, name) {
return (list || []).filter(function (n) {
return n !== name
})
}
// Handler for drag start - disables panel background clicks // Handler for drag start - disables panel background clicks
function handleDragStart() { function handleDragStart() {
@ -25,65 +40,153 @@ ColumnLayout {
} }
} }
ColumnLayout { NHeader {
spacing: Style.marginL * scaling label: "Appearance"
description: "Configure bar appearance and positioning."
}
RowLayout { RowLayout {
NComboBox { NComboBox {
Layout.fillWidth: true Layout.fillWidth: true
label: "Bar Position" label: "Bar Position"
description: "Choose where to place the bar on the screen." description: "Choose where to place the bar on the screen."
model: ListModel { model: ListModel {
ListElement { ListElement {
key: "top" key: "top"
name: "Top" name: "Top"
} }
ListElement { ListElement {
key: "bottom" key: "bottom"
name: "Bottom" name: "Bottom"
} }
ListElement {
key: "left"
name: "Left"
}
ListElement {
key: "right"
name: "Right"
} }
currentKey: Settings.data.bar.position
onSelected: key => Settings.data.bar.position = key
} }
currentKey: Settings.data.bar.position
onSelected: key => Settings.data.bar.position = key
}
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NLabel {
label: "Background Opacity"
description: "Adjust the background opacity of the bar."
} }
ColumnLayout { NValueSlider {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.backgroundOpacity
onMoved: value => Settings.data.bar.backgroundOpacity = value
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
}
}
NToggle {
Layout.fillWidth: true
label: "Floating Bar"
description: "Make the bar float with rounded corners and margins. Screen corners will move to screen edges."
checked: Settings.data.bar.floating
onToggled: checked => Settings.data.bar.floating = checked
}
NText { // Floating bar options - only show when floating is enabled
text: "Background Opacity" ColumnLayout {
font.pointSize: Style.fontSizeL * scaling visible: Settings.data.bar.floating
font.weight: Style.fontWeightBold spacing: Style.marginS * scaling
color: Color.mOnSurface Layout.fillWidth: true
}
NText { NLabel {
text: "Adjust the background opacity of the bar." label: "Margins"
font.pointSize: Style.fontSizeXS * scaling description: "Adjust the margins around the floating bar."
color: Color.mOnSurfaceVariant }
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout { RowLayout {
NSlider { Layout.fillWidth: true
spacing: Style.marginL * scaling
ColumnLayout {
spacing: Style.marginXXS * scaling
NText {
text: "Vertical"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
}
NValueSlider {
Layout.fillWidth: true Layout.fillWidth: true
from: 0 from: 0
to: 1 to: 1
stepSize: 0.01 stepSize: 0.01
value: Settings.data.bar.backgroundOpacity value: Settings.data.bar.marginVertical
onMoved: Settings.data.bar.backgroundOpacity = value onMoved: value => Settings.data.bar.marginVertical = value
cutoutColor: Color.mSurface text: Math.round(Settings.data.bar.marginVertical * 100) + "%"
} }
}
ColumnLayout {
spacing: Style.marginXXS * scaling
NText { NText {
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%" text: "Horizontal"
Layout.alignment: Qt.AlignVCenter font.pointSize: Style.fontSizeXS * scaling
Layout.leftMargin: Style.marginS * scaling color: Color.mOnSurfaceVariant
color: Color.mOnSurface
} }
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.marginHorizontal
onMoved: value => Settings.data.bar.marginHorizontal = value
text: Math.round(Settings.data.bar.marginHorizontal * 100) + "%"
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Monitor Configuration
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NHeader {
label: "Monitors Configuration"
description: "Choose which monitors should display the bar."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
} else {
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
}
}
} }
} }
} }
@ -99,20 +202,9 @@ ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Widgets Positioning" label: "Widgets Positioning"
font.pointSize: Style.fontSizeXXL * scaling description: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
NText {
text: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
} }
// Bar Sections // Bar Sections
@ -201,8 +293,7 @@ ColumnLayout {
} }
function _reorderWidgetInSection(section, fromIndex, toIndex) { function _reorderWidgetInSection(section, fromIndex, toIndex) {
if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) {
&& toIndex < Settings.data.bar.widgets[section].length) {
// Create a new array to avoid modifying the original // Create a new array to avoid modifying the original
var newArray = Settings.data.bar.widgets[section].slice() var newArray = Settings.data.bar.widgets[section].slice()

View file

@ -1,341 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
import qs.Widgets
ColumnLayout {
id: root
// Time dropdown options (00:00 .. 23:30)
ListModel {
id: timeOptions
}
Component.onCompleted: {
for (var h = 0; h < 24; h++) {
for (var m = 0; m < 60; m += 30) {
var hh = ("0" + h).slice(-2)
var mm = ("0" + m).slice(-2)
var key = hh + ":" + mm
timeOptions.append({
"key": key,
"name": key
})
}
}
}
// Check for wlsunset availability when enabling Night Light
Process {
id: wlsunsetCheck
command: ["which", "wlsunset"]
running: false
onExited: function (exitCode) {
if (exitCode === 0) {
Settings.data.nightLight.enabled = true
NightLightService.apply()
ToastService.showNotice("Night Light", "Enabled")
} else {
Settings.data.nightLight.enabled = false
ToastService.showWarning("Night Light", "wlsunset not installed")
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
spacing: Style.marginL * scaling
// Brightness Step Section
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NSpinBox {
Layout.fillWidth: true
label: "Brightness Step Size"
description: "Adjust the step size for brightness changes (scroll wheel, keyboard shortcuts)."
minimum: 1
maximum: 50
value: Settings.data.brightness.brightnessStep
stepSize: 1
suffix: "%"
onValueChanged: {
Settings.data.brightness.brightnessStep = value
}
}
}
// Monitor Overview Section
ColumnLayout {
spacing: Style.marginL * scaling
NLabel {
label: "Monitors Brightness Control"
description: "Current brightness levels for all detected monitors."
}
// Single monitor display using the same data source as the bar icon
Repeater {
model: BrightnessService.monitors
Rectangle {
Layout.fillWidth: true
radius: Style.radiusM * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
ColumnLayout {
id: contentCol
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NText {
text: `${model.modelData.name} [${model.modelData.model}]`
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
Item {
Layout.fillWidth: true
}
NText {
text: model.method
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignRight
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NText {
text: "Brightness:"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurface
}
NSlider {
Layout.fillWidth: true
from: 0
to: 1
value: model.brightness
stepSize: 0.05
onPressedChanged: {
if (!pressed) {
var monitor = BrightnessService.getMonitorForScreen(model.modelData)
monitor.setBrightness(value)
}
}
}
NText {
text: Math.round(model.brightness * 100) + "%"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.alignment: Qt.AlignRight
}
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Night Light Section
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.fillWidth: true
NText {
text: "Night Light"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NText {
text: "Reduce blue light emission to help you sleep better and reduce eye strain."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
NToggle {
label: "Enable Night Light"
description: "Apply a warm color filter to reduce blue light emission."
checked: Settings.data.nightLight.enabled
onToggled: checked => {
if (checked) {
// Verify wlsunset exists before enabling
wlsunsetCheck.running = true
} else {
Settings.data.nightLight.enabled = false
Settings.data.nightLight.forced = false
NightLightService.apply()
ToastService.showNotice("Night Light", "Disabled")
}
}
}
// Temperature
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.alignment: Qt.AlignVCenter
NLabel {
label: "Color temperature"
description: "Choose two temperatures in Kelvin."
}
RowLayout {
visible: Settings.data.nightLight.enabled
spacing: Style.marginM * scaling
Layout.fillWidth: false
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
NText {
text: "Night"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NTextInput {
text: Settings.data.nightLight.nightTemp
inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter
onEditingFinished: {
var nightTemp = parseInt(text)
var dayTemp = parseInt(Settings.data.nightLight.dayTemp)
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
// Clamp value between [1000 .. (dayTemp-500)]
var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp))
text = Settings.data.nightLight.nightTemp = clampedValue.toString()
}
}
}
Item {}
NText {
text: "Day"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NTextInput {
text: Settings.data.nightLight.dayTemp
inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter
onEditingFinished: {
var dayTemp = parseInt(text)
var nightTemp = parseInt(Settings.data.nightLight.nightTemp)
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
// Clamp value between [(nightTemp+500) .. 6500]
var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp))
text = Settings.data.nightLight.dayTemp = clampedValue.toString()
}
}
}
}
}
NToggle {
label: "Automatic Scheduling"
description: `Based on the sunset and sunrise time in <i>${LocationService.stableName}</i> - recommended.`
checked: Settings.data.nightLight.autoSchedule
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
visible: Settings.data.nightLight.enabled
}
// Schedule settings
ColumnLayout {
spacing: Style.marginXS * scaling
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule
&& !Settings.data.nightLight.forced
RowLayout {
Layout.fillWidth: false
spacing: Style.marginM * scaling
NLabel {
label: "Manual Scheduling"
}
Item {// add a little more spacing
}
NText {
text: "Sunrise Time"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.manualSunrise
placeholder: "Select start time"
onSelected: key => Settings.data.nightLight.manualSunrise = key
minimumWidth: 120 * scaling
}
Item {// add a little more spacing
}
NText {
text: "Sunset Time"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.manualSunset
placeholder: "Select stop time"
onSelected: key => Settings.data.nightLight.manualSunset = key
minimumWidth: 120 * scaling
}
}
}
// Force activation toggle
NToggle {
label: "Force activation"
description: "Immediately apply night temperature without scheduling or fade."
checked: Settings.data.nightLight.forced
onToggled: checked => {
Settings.data.nightLight.forced = checked
if (checked && !Settings.data.nightLight.enabled) {
// Ensure enabled when forcing
wlsunsetCheck.running = true
} else {
NightLightService.apply()
}
}
visible: Settings.data.nightLight.enabled
}
}

View file

@ -8,7 +8,7 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: 0 spacing: Style.marginL * scaling
// Cache for scheme JSON (can be flat or {dark, light}) // Cache for scheme JSON (can be flat or {dark, light})
property var schemeColorsCache: ({}) property var schemeColorsCache: ({})
@ -105,39 +105,39 @@ ColumnLayout {
} }
// Main Toggles - Dark Mode / Matugen // Main Toggles - Dark Mode / Matugen
ColumnLayout { NHeader {
spacing: Style.marginL * scaling label: "Behavior"
Layout.fillWidth: true description: "Main settings for Noctalia's colors."
}
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
NToggle { NToggle {
label: "Dark Mode" label: "Dark Mode"
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available." description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
checked: Settings.data.colorSchemes.darkMode checked: Settings.data.colorSchemes.darkMode
enabled: true enabled: true
onToggled: checked => Settings.data.colorSchemes.darkMode = checked onToggled: checked => Settings.data.colorSchemes.darkMode = checked
} }
// Use Matugen // Use Matugen
NToggle { NToggle {
label: "Enable Matugen" label: "Enable Matugen"
description: "Automatically generate colors based on your active wallpaper." description: "Automatically generate colors based on your active wallpaper."
checked: Settings.data.colorSchemes.useWallpaperColors checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => { onToggled: checked => {
if (checked) { if (checked) {
// Check if matugen is installed // Check if matugen is installed
matugenCheck.running = true matugenCheck.running = true
} else { } else {
Settings.data.colorSchemes.useWallpaperColors = false Settings.data.colorSchemes.useWallpaperColors = false
ToastService.showNotice("Matugen", "Disabled") ToastService.showNotice("Matugen", "Disabled")
if (Settings.data.colorSchemes.predefinedScheme) { if (Settings.data.colorSchemes.predefinedScheme) {
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
} }
} }
} }
} }
NDivider { NDivider {
@ -151,19 +151,9 @@ ColumnLayout {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Predefined Color Schemes" label: "Predefined Color Schemes"
font.pointSize: Style.fontSizeXXL * scaling description: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper."
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NText {
text: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
wrapMode: Text.WordWrap
} }
// Color Schemes Grid // Color Schemes Grid
@ -186,9 +176,7 @@ ColumnLayout {
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: getSchemeColor(modelData, "mSurface") color: getSchemeColor(modelData, "mSurface")
border.width: Math.max(1, Style.borderL * scaling) border.width: Math.max(1, Style.borderL * scaling)
border.color: (!Settings.data.colorSchemes.useWallpaperColors border.color: (!Settings.data.colorSchemes.useWallpaperColors && (Settings.data.colorSchemes.predefinedScheme === modelData.split("/").pop().replace(".json", ""))) ? Color.mSecondary : Color.mOutline
&& (Settings.data.colorSchemes.predefinedScheme === modelData.split("/").pop().replace(
".json", ""))) ? Color.mPrimary : Color.mOutline
scale: root.cardScaleLow scale: root.cardScaleLow
// Mouse area for selection // Mouse area for selection
@ -281,23 +269,21 @@ ColumnLayout {
// Selection indicator (Checkmark) // Selection indicator (Checkmark)
Rectangle { Rectangle {
visible: !Settings.data.colorSchemes.useWallpaperColors visible: !Settings.data.colorSchemes.useWallpaperColors && (Settings.data.colorSchemes.predefinedScheme === schemePath.split("/").pop().replace(".json", ""))
&& (Settings.data.colorSchemes.predefinedScheme === schemePath.split("/").pop().replace(".json",
""))
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: Style.marginS * scaling anchors.margins: Style.marginS * scaling
width: 24 * scaling width: 28 * scaling
height: 24 * scaling height: 28 * scaling
radius: width * 0.5 radius: width * 0.5
color: Color.mPrimary color: Color.mSecondary
NText { NIcon {
anchors.centerIn: parent icon: "check"
text: "✓" font.pointSize: Style.fontSizeM * scaling
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnPrimary color: Color.mOnSecondary
anchors.centerIn: parent
} }
} }
@ -332,146 +318,187 @@ ColumnLayout {
visible: Settings.data.colorSchemes.useWallpaperColors visible: Settings.data.colorSchemes.useWallpaperColors
} }
// Matugen template toggles (moved from MatugenTab) // Matugen template toggles organized by category
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
visible: Settings.data.colorSchemes.useWallpaperColors visible: Settings.data.colorSchemes.useWallpaperColors
spacing: Style.marginL * scaling
ColumnLayout { // UI Components
spacing: Style.marginS * scaling NCollapsible {
Layout.fillWidth: true Layout.fillWidth: true
label: "UI"
description: "Desktop environment and UI toolkit theming."
defaultExpanded: false
NText { NCheckbox {
text: "Matugen Templates" label: "GTK 4 (libadwaita)"
font.pointSize: Style.fontSizeXXL * scaling description: "Write ~/.config/gtk-4.0/gtk.css"
font.weight: Style.fontWeightBold checked: Settings.data.matugen.gtk4
color: Color.mSecondary onToggled: checked => {
Settings.data.matugen.gtk4 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
} }
NText { NCheckbox {
text: "Select which external components Matugen should apply theming to." label: "GTK 3"
font.pointSize: Style.fontSizeM * scaling description: "Write ~/.config/gtk-3.0/gtk.css"
color: Color.mOnSurfaceVariant checked: Settings.data.matugen.gtk3
Layout.fillWidth: true onToggled: checked => {
wrapMode: Text.WordWrap Settings.data.matugen.gtk3 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Qt6ct"
description: "Write ~/.config/qt6ct/colors/noctalia.conf"
checked: Settings.data.matugen.qt6
onToggled: checked => {
Settings.data.matugen.qt6 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Qt5ct"
description: "Write ~/.config/qt5ct/colors/noctalia.conf"
checked: Settings.data.matugen.qt5
onToggled: checked => {
Settings.data.matugen.qt5 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
} }
} }
NCheckbox { // Terminal Emulators
label: "GTK 4 (libadwaita)" NCollapsible {
description: "Write ~/.config/gtk-4.0/gtk.css"
checked: Settings.data.matugen.gtk4
onToggled: checked => {
Settings.data.matugen.gtk4 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "GTK 3"
description: "Write ~/.config/gtk-3.0/gtk.css"
checked: Settings.data.matugen.gtk3
onToggled: checked => {
Settings.data.matugen.gtk3 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Qt6ct"
description: "Write ~/.config/qt6ct/colors/noctalia.conf"
checked: Settings.data.matugen.qt6
onToggled: checked => {
Settings.data.matugen.qt6 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Qt5ct"
description: "Write ~/.config/qt5ct/colors/noctalia.conf"
checked: Settings.data.matugen.qt5
onToggled: checked => {
Settings.data.matugen.qt5 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Kitty"
description: "Write ~/.config/kitty/themes/noctalia.conf and reload"
checked: Settings.data.matugen.kitty
onToggled: checked => {
Settings.data.matugen.kitty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Ghostty"
description: "Write ~/.config/ghostty/themes/noctalia and reload"
checked: Settings.data.matugen.ghostty
onToggled: checked => {
Settings.data.matugen.ghostty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Foot"
description: "Write ~/.config/foot/themes/noctalia and reload"
checked: Settings.data.matugen.foot
onToggled: checked => {
Settings.data.matugen.foot = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Fuzzel"
description: "Write ~/.config/fuzzel/themes/noctalia and reload"
checked: Settings.data.matugen.fuzzel
onToggled: checked => {
Settings.data.matugen.fuzzel = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Vesktop"
description: "Write ~/.config/vesktop/themes/noctalia.theme.css"
checked: Settings.data.matugen.vesktop
onToggled: checked => {
Settings.data.matugen.vesktop = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling label: "Terminal"
Layout.bottomMargin: Style.marginM * scaling description: "Terminal emulator theming."
defaultExpanded: false
NCheckbox {
label: "Kitty"
description: ProgramCheckerService.kittyAvailable ? "Write ~/.config/kitty/themes/noctalia.conf and reload" : "Requires kitty terminal to be installed"
checked: Settings.data.matugen.kitty
enabled: ProgramCheckerService.kittyAvailable
opacity: ProgramCheckerService.kittyAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.kittyAvailable) {
Settings.data.matugen.kitty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Ghostty"
description: ProgramCheckerService.ghosttyAvailable ? "Write ~/.config/ghostty/themes/noctalia and reload" : "Requires ghostty terminal to be installed"
checked: Settings.data.matugen.ghostty
enabled: ProgramCheckerService.ghosttyAvailable
opacity: ProgramCheckerService.ghosttyAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.ghosttyAvailable) {
Settings.data.matugen.ghostty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Foot"
description: ProgramCheckerService.footAvailable ? "Write ~/.config/foot/themes/noctalia and reload" : "Requires foot terminal to be installed"
checked: Settings.data.matugen.foot
enabled: ProgramCheckerService.footAvailable
opacity: ProgramCheckerService.footAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.footAvailable) {
Settings.data.matugen.foot = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
} }
NCheckbox { // Applications
label: "User Templates" NCollapsible {
description: "Enable user-defined Matugen config from ~/.config/matugen/config.toml" Layout.fillWidth: true
checked: Settings.data.matugen.enableUserTemplates label: "Programs"
onToggled: checked => { description: "Application-specific theming."
Settings.data.matugen.enableUserTemplates = checked defaultExpanded: false
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper() NCheckbox {
} label: "Fuzzel"
description: ProgramCheckerService.fuzzelAvailable ? "Write ~/.config/fuzzel/themes/noctalia and reload" : "Requires fuzzel launcher to be installed"
checked: Settings.data.matugen.fuzzel
enabled: ProgramCheckerService.fuzzelAvailable
opacity: ProgramCheckerService.fuzzelAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.fuzzelAvailable) {
Settings.data.matugen.fuzzel = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Vesktop"
description: ProgramCheckerService.vesktopAvailable ? "Write ~/.config/vesktop/themes/noctalia.theme.css" : "Requires vesktop Discord client to be installed"
checked: Settings.data.matugen.vesktop
enabled: ProgramCheckerService.vesktopAvailable
opacity: ProgramCheckerService.vesktopAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.vesktopAvailable) {
Settings.data.matugen.vesktop = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Pywalfox (Firefox)"
description: ProgramCheckerService.pywalfoxAvailable ? "Write ~/.cache/wal/colors.json and run pywalfox update" : "Requires pywalfox package to be installed"
checked: Settings.data.matugen.pywalfox
enabled: ProgramCheckerService.pywalfoxAvailable
opacity: ProgramCheckerService.pywalfoxAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.pywalfoxAvailable) {
Settings.data.matugen.pywalfox = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
}
// Miscellaneous
NCollapsible {
Layout.fillWidth: true
label: "Misc"
description: "Additional configuration options."
defaultExpanded: false
NCheckbox {
label: "User Templates"
description: "Enable user-defined Matugen config from ~/.config/matugen/config.toml"
checked: Settings.data.matugen.enableUserTemplates
onToggled: checked => {
Settings.data.matugen.enableUserTemplates = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
} }
} }
} }

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Io
import qs.Commons import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@ -9,49 +10,68 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
// Helper functions to update arrays immutably // Time dropdown options (00:00 .. 23:30)
function addMonitor(list, name) { ListModel {
const arr = (list || []).slice() id: timeOptions
if (!arr.includes(name))
arr.push(name)
return arr
} }
function removeMonitor(list, name) { Component.onCompleted: {
return (list || []).filter(function (n) { for (var h = 0; h < 24; h++) {
return n !== name for (var m = 0; m < 60; m += 30) {
}) var hh = ("0" + h).slice(-2)
var mm = ("0" + m).slice(-2)
var key = hh + ":" + mm
timeOptions.append({
"key": key,
"name": key
})
}
}
} }
NText { // Check for wlsunset availability when enabling Night Light
text: "Monitor-specific configuration" Process {
font.pointSize: Style.fontSizeL * scaling id: wlsunsetCheck
font.weight: Style.fontWeightBold command: ["which", "wlsunset"]
running: false
onExited: function (exitCode) {
if (exitCode === 0) {
Settings.data.nightLight.enabled = true
NightLightService.apply()
ToastService.showNotice("Night Light", "Enabled")
} else {
Settings.data.nightLight.enabled = false
ToastService.showWarning("Night Light", "wlsunset not installed")
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
} }
NText { spacing: Style.marginL * scaling
text: "Bars and notifications appear on all displays by default. Choose specific displays below to limit where they're shown."
font.pointSize: Style.fontSizeM * scaling NHeader {
color: Color.mOnSurfaceVariant label: "Monitor-specific configuration"
wrapMode: Text.WordWrap description: "Configure scaling and brightness settings individually for each connected display."
Layout.fillWidth: true
} }
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.topMargin: Style.marginL * scaling
Repeater { Repeater {
model: Quickshell.screens || [] model: Quickshell.screens || []
delegate: Rectangle { delegate: Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 550 * scaling implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: Color.mSurface color: Color.mSurfaceVariant
border.color: Color.mOutline border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
property real localScaling: ScalingService.getScreenScale(modelData) property real localScaling: ScalingService.getScreenScale(modelData)
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
Connections { Connections {
target: ScalingService target: ScalingService
function onScaleChanged(screenName, scale) { function onScaleChanged(screenName, scale) {
@ -68,122 +88,100 @@ ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
NText { NText {
text: (modelData.name || "Unknown") text: (`${modelData.name}: ${modelData.model}` || "Unknown")
font.pointSize: Style.fontSizeXL * scaling font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mSecondary color: Color.mPrimary
} }
NText { NText {
text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})` text: `Resolution: ${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
} }
// Scale
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NToggle { RowLayout {
Layout.fillWidth: true spacing: Style.marginM * scaling
label: "Bar"
description: "Enable the bar on this monitor."
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
} else {
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
}
}
}
NToggle {
Layout.fillWidth: true
label: "Notifications"
description: "Enable notifications on this monitor."
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors,
modelData.name)
} else {
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors,
modelData.name)
}
}
}
NToggle {
Layout.fillWidth: true
label: "Dock"
description: "Enable the dock on this monitor."
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name)
} else {
Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name)
}
}
}
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
RowLayout { NText {
Layout.fillWidth: true text: "Scale"
spacing: Style.marginL * scaling Layout.preferredWidth: 80 * scaling
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Scale"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Scale the user interface on this monitor."
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
NText {
text: `${Math.round(localScaling * 100)}%`
Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: 50 * scaling
horizontalAlignment: Text.AlignRight
}
} }
RowLayout { NValueSlider {
spacing: Style.marginS * scaling id: scaleSlider
from: 0.7
to: 1.8
stepSize: 0.01
value: localScaling
onPressedChanged: (pressed, value) => ScalingService.setScreenScale(modelData, value)
text: `${Math.round(localScaling * 100)}%`
Layout.fillWidth: true Layout.fillWidth: true
}
NSlider { // Reset button container
id: scaleSlider Item {
from: 0.7 Layout.preferredWidth: 40 * scaling
to: 1.8 Layout.preferredHeight: 30 * scaling
stepSize: 0.01
value: localScaling
onPressedChanged: ScalingService.setScreenScale(modelData, value)
Layout.fillWidth: true
Layout.minimumWidth: 150 * scaling
}
NIconButton { NIconButton {
icon: "refresh" icon: "refresh"
sizeRatio: 0.8
tooltipText: "Reset scaling" tooltipText: "Reset scaling"
onClicked: ScalingService.setScreenScale(modelData, 1.0) onClicked: ScalingService.setScreenScale(modelData, 1.0)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
// Brightness
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NText {
text: "Brightness"
Layout.preferredWidth: 80 * scaling
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5
stepSize: 0.01
onPressedChanged: (pressed, value) => brightnessMonitor.setBrightness(value)
text: brightnessMonitor ? Math.round(brightnessMonitor.brightness * 100) + "%" : "N/A"
}
// Empty container to match scale row layout
Item {
Layout.preferredWidth: 40 * scaling
Layout.preferredHeight: 30 * scaling
// Method text positioned in the button area
NText {
text: brightnessMonitor ? brightnessMonitor.method : ""
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
} }
} }
} }
@ -192,4 +190,216 @@ ColumnLayout {
} }
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Brightness Section
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NHeader {
label: "Brightness"
description: "Adjust brightness related settings."
}
// Brightness Step Section
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NSpinBox {
Layout.fillWidth: true
label: "Brightness Step Size"
description: "Adjust the step size for brightness changes (scroll wheel and keyboard shortcuts)."
minimum: 1
maximum: 50
value: Settings.data.brightness.brightnessStep
stepSize: 1
suffix: "%"
onValueChanged: Settings.data.brightness.brightnessStep = value
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Night Light Section
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.fillWidth: true
NHeader {
label: "Night Light"
description: "Reduce blue light emission to help you sleep better and reduce eye strain."
}
}
NToggle {
label: "Enable Night Light"
description: "Apply a warm color filter to reduce blue light emission."
checked: Settings.data.nightLight.enabled
onToggled: checked => {
if (checked) {
// Verify wlsunset exists before enabling
wlsunsetCheck.running = true
} else {
Settings.data.nightLight.enabled = false
Settings.data.nightLight.forced = false
NightLightService.apply()
ToastService.showNotice("Night Light", "Disabled")
}
}
}
// Temperature
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.alignment: Qt.AlignVCenter
NLabel {
label: "Color temperature"
description: "Choose two temperatures in Kelvin."
}
RowLayout {
visible: Settings.data.nightLight.enabled
spacing: Style.marginM * scaling
Layout.fillWidth: false
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
NText {
text: "Night"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NTextInput {
text: Settings.data.nightLight.nightTemp
inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter
onEditingFinished: {
var nightTemp = parseInt(text)
var dayTemp = parseInt(Settings.data.nightLight.dayTemp)
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
// Clamp value between [1000 .. (dayTemp-500)]
var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp))
text = Settings.data.nightLight.nightTemp = clampedValue.toString()
}
}
}
Item {}
NText {
text: "Day"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NTextInput {
text: Settings.data.nightLight.dayTemp
inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter
onEditingFinished: {
var dayTemp = parseInt(text)
var nightTemp = parseInt(Settings.data.nightLight.nightTemp)
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
// Clamp value between [(nightTemp+500) .. 6500]
var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp))
text = Settings.data.nightLight.dayTemp = clampedValue.toString()
}
}
}
}
}
NToggle {
label: "Automatic Scheduling"
description: `Based on the sunset and sunrise time in <i>${LocationService.stableName}</i> - recommended.`
checked: Settings.data.nightLight.autoSchedule
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
visible: Settings.data.nightLight.enabled
}
// Schedule settings
ColumnLayout {
spacing: Style.marginXS * scaling
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
RowLayout {
Layout.fillWidth: false
spacing: Style.marginM * scaling
NLabel {
label: "Manual Scheduling"
}
Item {// add a little more spacing
}
NText {
text: "Sunrise Time"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.manualSunrise
placeholder: "Select start time"
onSelected: key => Settings.data.nightLight.manualSunrise = key
minimumWidth: 120 * scaling
}
Item {// add a little more spacing
}
NText {
text: "Sunset Time"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.manualSunset
placeholder: "Select stop time"
onSelected: key => Settings.data.nightLight.manualSunset = key
minimumWidth: 120 * scaling
}
}
}
// Force activation toggle
NToggle {
label: "Force activation"
description: "Immediately apply night temperature without scheduling or fade."
checked: Settings.data.nightLight.forced
onToggled: checked => {
Settings.data.nightLight.forced = checked
if (checked && !Settings.data.nightLight.enabled) {
// Ensure enabled when forcing
wlsunsetCheck.running = true
} else {
NightLightService.apply()
}
}
visible: Settings.data.nightLight.enabled
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -0,0 +1,122 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL * scaling
// Helper functions to update arrays immutably
function addMonitor(list, name) {
const arr = (list || []).slice()
if (!arr.includes(name))
arr.push(name)
return arr
}
function removeMonitor(list, name) {
return (list || []).filter(function (n) {
return n !== name
})
}
NHeader {
label: "Appearance"
description: "Configure dock behavior and appearance."
}
NToggle {
label: "Auto-hide"
description: "Automatically hide when not in use."
checked: Settings.data.dock.autoHide
onToggled: checked => Settings.data.dock.autoHide = checked
}
NToggle {
label: "Exclusive Zone"
description: "Ensure windows don't open underneath."
checked: Settings.data.dock.exclusive
onToggled: checked => Settings.data.dock.exclusive = checked
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NLabel {
label: "Background Opacity"
description: "Adjust the background opacity."
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.dock.backgroundOpacity
onMoved: value => Settings.data.dock.backgroundOpacity = value
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
}
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NLabel {
label: "Dock Floating Distance"
description: "Adjust the floating distance from the screen edge."
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 4
stepSize: 0.01
value: Settings.data.dock.floatingRatio
onMoved: value => Settings.data.dock.floatingRatio = value
text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%"
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Monitor Configuration
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NHeader {
label: "Monitors Configuration"
description: "Choose which monitors should display the dock."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name)
} else {
Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name)
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
}

View file

@ -9,6 +9,11 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
NHeader {
label: "Profile"
description: "Configure your user profile and avatar settings."
}
// Profile section // Profile section
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
@ -48,19 +53,9 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "User Interface" label: "User Interface"
font.pointSize: Style.fontSizeXXL * scaling description: "Main settings for the user interface."
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
NToggle {
label: "Show Corners"
description: "Display rounded corners on the edge of the screen."
checked: Settings.data.general.showScreenCorners
onToggled: checked => Settings.data.general.showScreenCorners = checked
} }
NToggle { NToggle {
@ -79,23 +74,14 @@ ColumnLayout {
description: "Adjust the rounded border of all UI elements." description: "Adjust the rounded border of all UI elements."
} }
RowLayout { NValueSlider {
NSlider { Layout.fillWidth: true
Layout.fillWidth: true from: 0
from: 0 to: 1
to: 1 stepSize: 0.01
stepSize: 0.01 value: Settings.data.general.radiusRatio
value: Settings.data.general.radiusRatio onMoved: value => Settings.data.general.radiusRatio = value
onMoved: Settings.data.general.radiusRatio = value text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
} }
} }
@ -109,26 +95,18 @@ ColumnLayout {
description: "Adjust global animation speed." description: "Adjust global animation speed."
} }
RowLayout { NValueSlider {
NSlider { Layout.fillWidth: true
Layout.fillWidth: true from: 0.1
from: 0.1 to: 2.0
to: 2.0 stepSize: 0.01
stepSize: 0.01 value: Settings.data.general.animationSpeed
value: Settings.data.general.animationSpeed onMoved: value => Settings.data.general.animationSpeed = value
onMoved: Settings.data.general.animationSpeed = value text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
cutoutColor: Color.mSurface
}
NText {
text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
} }
} }
} }
NDivider { NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling Layout.topMargin: Style.marginXL * scaling
@ -139,57 +117,42 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Dock" label: "Screen Corners"
font.pointSize: Style.fontSizeXXL * scaling description: "Customize screen corner rounding and visual effects."
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
NToggle { NToggle {
label: "Auto-hide Dock" label: "Show Screen Corners"
description: "Automatically hide the dock when not in use." description: "Display rounded corners on the edge of the screen."
checked: Settings.data.dock.autoHide checked: Settings.data.general.showScreenCorners
onToggled: checked => Settings.data.dock.autoHide = checked onToggled: checked => Settings.data.general.showScreenCorners = checked
}
NToggle {
label: "Solid Black Corners"
description: "Force screen corners to always render as solid black."
checked: Settings.data.general.forceBlackScreenCorners
onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked
} }
ColumnLayout { ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NLabel {
text: "Dock Background Opacity" label: "Screen Corners Radius"
font.pointSize: Style.fontSizeL * scaling description: "Adjust the rounded corners of the screen."
font.weight: Style.fontWeightBold
color: Color.mOnSurface
} }
NText { NValueSlider {
text: "Adjust the background opacity of the dock."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
} from: 0
to: 2
RowLayout { stepSize: 0.01
NSlider { value: Settings.data.general.screenRadiusRatio
Layout.fillWidth: true onMoved: value => Settings.data.general.screenRadiusRatio = value
from: 0 text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%"
to: 1
stepSize: 0.01
value: Settings.data.dock.backgroundOpacity
onMoved: Settings.data.dock.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
} }
} }
} }
@ -203,12 +166,10 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
text: "Fonts" NHeader {
font.pointSize: Style.fontSizeXXL * scaling label: "Fonts"
font.weight: Style.fontWeightBold description: "Configure interface typography."
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// Font configuration section // Font configuration section
@ -216,12 +177,13 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NComboBox { NSearchableComboBox {
label: "Default Font" label: "Default Font"
description: "Main font used throughout the interface." description: "Main font used throughout the interface."
model: FontService.availableFonts model: FontService.availableFonts
currentKey: Settings.data.ui.fontDefault currentKey: Settings.data.ui.fontDefault
placeholder: "Select default font..." placeholder: "Select default font..."
searchPlaceholder: "Search fonts..."
popupHeight: 420 * scaling popupHeight: 420 * scaling
minimumWidth: 300 * scaling minimumWidth: 300 * scaling
onSelected: function (key) { onSelected: function (key) {
@ -229,12 +191,13 @@ ColumnLayout {
} }
} }
NComboBox { NSearchableComboBox {
label: "Fixed Width Font" label: "Fixed Width Font"
description: "Monospace font used for terminal and code display." description: "Monospace font used for terminal and code display."
model: FontService.monospaceFonts model: FontService.monospaceFonts
currentKey: Settings.data.ui.fontFixed currentKey: Settings.data.ui.fontFixed
placeholder: "Select monospace font..." placeholder: "Select monospace font..."
searchPlaceholder: "Search monospace fonts..."
popupHeight: 320 * scaling popupHeight: 320 * scaling
minimumWidth: 300 * scaling minimumWidth: 300 * scaling
onSelected: function (key) { onSelected: function (key) {
@ -242,12 +205,13 @@ ColumnLayout {
} }
} }
NComboBox { NSearchableComboBox {
label: "Billboard Font" label: "Billboard Font"
description: "Large font used for clocks and prominent displays." description: "Large font used for clocks and prominent displays."
model: FontService.displayFonts model: FontService.displayFonts
currentKey: Settings.data.ui.fontBillboard currentKey: Settings.data.ui.fontBillboard
placeholder: "Select display font..." placeholder: "Select display font..."
searchPlaceholder: "Search display fonts..."
popupHeight: 320 * scaling popupHeight: 320 * scaling
minimumWidth: 300 * scaling minimumWidth: 300 * scaling
onSelected: function (key) { onSelected: function (key) {

View file

@ -10,6 +10,11 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
width: root.width width: root.width
NHeader {
label: "System Hooks"
description: "Configure commands to be executed when system events occur."
}
// Enable/Disable Toggle // Enable/Disable Toggle
NToggle { NToggle {
label: "Enable Hooks" label: "Enable Hooks"

View file

@ -7,104 +7,97 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: Style.marginL * scaling
NHeader {
label: "Appearance"
description: "Configure the launcher behavior and appearance."
}
NComboBox {
id: launcherPosition
label: "Position"
description: "Choose where the Launcher panel appears."
Layout.fillWidth: true
model: ListModel {
ListElement {
key: "center"
name: "Center (default)"
}
ListElement {
key: "top_left"
name: "Top Left"
}
ListElement {
key: "top_right"
name: "Top Right"
}
ListElement {
key: "bottom_left"
name: "Bottom Left"
}
ListElement {
key: "bottom_right"
name: "Bottom Right"
}
ListElement {
key: "bottom_center"
name: "Bottom Center"
}
ListElement {
key: "top_center"
name: "Top Center"
}
}
currentKey: Settings.data.appLauncher.position
onSelected: function (key) {
Settings.data.appLauncher.position = key
}
}
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NComboBox { NText {
id: launcherPosition text: "Background Opacity"
label: "Position" font.pointSize: Style.fontSizeL * scaling
description: "Choose where the Launcher panel appears." font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Adjust the background opacity of the launcher."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
model: ListModel {
ListElement {
key: "center"
name: "Center (default)"
}
ListElement {
key: "top_left"
name: "Top Left"
}
ListElement {
key: "top_right"
name: "Top Right"
}
ListElement {
key: "bottom_left"
name: "Bottom Left"
}
ListElement {
key: "bottom_right"
name: "Bottom Right"
}
ListElement {
key: "bottom_center"
name: "Bottom Center"
}
ListElement {
key: "top_center"
name: "Top Center"
}
}
currentKey: Settings.data.appLauncher.position
onSelected: function (key) {
Settings.data.appLauncher.position = key
}
} }
ColumnLayout { NValueSlider {
spacing: Style.marginXXS * scaling id: launcherBgOpacity
Layout.fillWidth: true Layout.fillWidth: true
from: 0.0
NText { to: 1.0
text: "Background Opacity" stepSize: 0.01
font.pointSize: Style.fontSizeL * scaling value: Settings.data.appLauncher.backgroundOpacity
font.weight: Style.fontWeightBold onMoved: value => Settings.data.appLauncher.backgroundOpacity = value
color: Color.mOnSurface text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%"
}
NText {
text: "Adjust the background opacity of the launcher."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
id: launcherBgOpacity
Layout.fillWidth: true
from: 0.0
to: 1.0
stepSize: 0.01
value: Settings.data.appLauncher.backgroundOpacity
onMoved: Settings.data.appLauncher.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
} }
}
NToggle { NToggle {
label: "Enable Clipboard History" label: "Enable Clipboard History"
description: "Show clipboard history in the launcher." description: "Show clipboard history in the launcher."
checked: Settings.data.appLauncher.enableClipboardHistory checked: Settings.data.appLauncher.enableClipboardHistory
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
} }
NToggle { NToggle {
label: "Use App2Unit for Launching" label: "Use App2Unit for Launching"
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration." description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
checked: Settings.data.appLauncher.useApp2Unit checked: Settings.data.appLauncher.useApp2Unit
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
}
} }
NDivider { NDivider {

View file

@ -11,6 +11,11 @@ ColumnLayout {
id: root id: root
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
NHeader {
label: "Network Settings"
description: "Configure Wi-Fi and Bluetooth connectivity options."
}
NToggle { NToggle {
label: "Enable Wi-Fi" label: "Enable Wi-Fi"
description: "Enable Wi-Fi connectivity." description: "Enable Wi-Fi connectivity."

View file

@ -0,0 +1,162 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
ColumnLayout {
id: root
// Helper functions to update arrays immutably
function addMonitor(list, name) {
const arr = (list || []).slice()
if (!arr.includes(name))
arr.push(name)
return arr
}
function removeMonitor(list, name) {
return (list || []).filter(function (n) {
return n !== name
})
}
// General Notification Settings
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NHeader {
label: "Appearance"
description: "Configure notifications appearance and behavior."
}
NToggle {
label: "Do Not Disturb"
description: "Disable all notification popups when enabled."
checked: Settings.data.notifications.doNotDisturb
onToggled: checked => Settings.data.notifications.doNotDisturb = checked
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Monitor Configuration
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NHeader {
label: "Monitors Configuration"
description: "Choose which monitors should display notifications."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, modelData.name)
} else {
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, modelData.name)
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Notification Duration Settings
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NHeader {
label: "Notification Duration"
description: "Configure how long notifications stay visible based on their urgency level."
}
// Low Urgency Duration
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NLabel {
label: "Low Urgency Duration"
description: "How long low priority notifications stay visible."
}
NValueSlider {
Layout.fillWidth: true
from: 1
to: 30
stepSize: 1
value: Settings.data.notifications.lowUrgencyDuration
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
text: Settings.data.notifications.lowUrgencyDuration + "s"
}
}
// Normal Urgency Duration
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NLabel {
label: "Normal Urgency Duration"
description: "How long normal priority notifications stay visible."
}
NValueSlider {
Layout.fillWidth: true
from: 1
to: 30
stepSize: 1
value: Settings.data.notifications.normalUrgencyDuration
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
text: Settings.data.notifications.normalUrgencyDuration + "s"
}
}
// Critical Urgency Duration
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NLabel {
label: "Critical Urgency Duration"
description: "How long critical priority notifications stay visible."
}
NValueSlider {
Layout.fillWidth: true
from: 1
to: 30
stepSize: 1
value: Settings.data.notifications.criticalUrgencyDuration
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
text: Settings.data.notifications.criticalUrgencyDuration + "s"
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
}

View file

@ -10,11 +10,15 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
NHeader {
label: "General Settings"
description: "Configure screen recording output and content."
}
// Output Directory // Output Directory
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginS * scaling
NTextInput { NTextInput {
label: "Output Directory" label: "Output Directory"
@ -53,12 +57,8 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Video Settings" label: "Video Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// Source // Source
@ -203,12 +203,8 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Audio Settings" label: "Audio Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// Audio Source // Audio Source

View file

@ -9,7 +9,6 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
width: parent.width width: parent.width
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
property list<string> wallpapersList: [] property list<string> wallpapersList: []
@ -42,11 +41,9 @@ ColumnLayout {
} }
// Current wallpaper display // Current wallpaper display
NText { NHeader {
text: "Current Wallpaper" label: "Current Wallpaper"
font.pointSize: Style.fontSizeXXL * scaling description: "Preview and manage your desktop background."
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
Rectangle { Rectangle {
@ -80,18 +77,9 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
// Wallpaper grid // Wallpaper grid
NText { NHeader {
text: "Wallpaper Selector" label: "Wallpaper Selector"
font.pointSize: Style.fontSizeXXL * scaling description: "Click on a wallpaper to set it as your current wallpaper."
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NText {
text: "Click on a wallpaper to set it as your current wallpaper."
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
} }
} }

View file

@ -9,6 +9,12 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: Style.marginL * scaling
NHeader {
label: "Wallpaper Settings"
description: "Control how wallpapers are managed and displayed."
}
NToggle { NToggle {
label: "Enable Wallpaper Management" label: "Enable Wallpaper Management"
@ -22,6 +28,7 @@ ColumnLayout {
visible: Settings.data.wallpaper.enabled visible: Settings.data.wallpaper.enabled
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NTextInput { NTextInput {
label: "Wallpaper Directory" label: "Wallpaper Directory"
description: "Path to your common wallpaper directory." description: "Path to your common wallpaper directory."
@ -61,7 +68,7 @@ ColumnLayout {
delegate: RowLayout { delegate: RowLayout {
NText { NText {
text: (modelData.name || "Unknown") text: (modelData.name || "Unknown")
color: Color.mSecondary color: Color.mPrimary
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
Layout.preferredWidth: 90 * scaling Layout.preferredWidth: 90 * scaling
} }
@ -89,11 +96,8 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Look & Feel" label: "Look & Feel"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
// Fill Mode // Fill Mode
@ -134,21 +138,14 @@ ColumnLayout {
description: "Duration of transition animations in seconds." description: "Duration of transition animations in seconds."
} }
RowLayout { NValueSlider {
spacing: Style.marginL * scaling Layout.fillWidth: true
NSlider { from: 500
Layout.fillWidth: true to: 10000
from: 500 stepSize: 100
to: 10000 value: Settings.data.wallpaper.transitionDuration
stepSize: 100 onMoved: value => Settings.data.wallpaper.transitionDuration = value
value: Settings.data.wallpaper.transitionDuration text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s"
onMoved: Settings.data.wallpaper.transitionDuration = value
cutoutColor: Color.mSurface
}
NText {
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s"
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
}
} }
} }
@ -159,20 +156,13 @@ ColumnLayout {
description: "Duration of transition animations in seconds." description: "Duration of transition animations in seconds."
} }
RowLayout { NValueSlider {
spacing: Style.marginL * scaling Layout.fillWidth: true
NSlider { from: 0.0
Layout.fillWidth: true to: 1.0
from: 0.0 value: Settings.data.wallpaper.transitionEdgeSmoothness
to: 1.0 onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value
value: Settings.data.wallpaper.transitionEdgeSmoothness text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
onMoved: Settings.data.wallpaper.transitionEdgeSmoothness = value
cutoutColor: Color.mSurface
}
NText {
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
}
} }
} }
} }
@ -189,11 +179,8 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Automation" label: "Automation"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
// Random Wallpaper // Random Wallpaper

View file

@ -7,6 +7,12 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: Style.marginL * scaling
NHeader {
label: "Your Location"
description: "Set your location for weather, time zones, and scheduling."
}
// Location section // Location section
RowLayout { RowLayout {
@ -57,11 +63,9 @@ ColumnLayout {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NHeader {
text: "Weather" label: "Weather"
font.pointSize: Style.fontSizeXXL * scaling description: "Configure weather display preferences and temperature units."
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
NToggle { NToggle {
@ -71,4 +75,10 @@ ColumnLayout {
onToggled: checked => Settings.data.location.useFahrenheit = checked onToggled: checked => Settings.data.location.useFahrenheit = checked
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -10,12 +10,8 @@ import qs.Widgets
NBox { NBox {
id: root id: root
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
Layout.fillHeight: true
anchors.margins: Style.marginL * scaling anchors.margins: Style.marginL * scaling
// No media player detected // No media player detected
@ -236,9 +232,7 @@ NBox {
return 0 return 0
return Math.max(0, Math.min(1, r)) return Math.max(0, Math.min(1, r))
} }
property real effectiveRatio: (MediaService.isSeeking property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
&& localSeekRatio >= 0) ? Math.max(0, Math.min(1,
localSeekRatio)) : progressRatio
// Debounced backend seek during drag // Debounced backend seek during drag
Timer { Timer {
@ -248,8 +242,7 @@ NBox {
onTriggered: { onTriggered: {
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) { if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio)) const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio))
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs( if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
MediaService.seekByRatio(next) MediaService.seekByRatio(next)
progressWrapper.lastSentSeekRatio = next progressWrapper.lastSentSeekRatio = next
} }
@ -265,7 +258,6 @@ NBox {
stepSize: 0 stepSize: 0
snapAlways: false snapAlways: false
enabled: MediaService.trackLength > 0 && MediaService.canSeek enabled: MediaService.trackLength > 0 && MediaService.canSeek
cutoutColor: Color.mSurface
heightRatio: 0.65 heightRatio: 0.65
onMoved: { onMoved: {

View file

@ -9,13 +9,11 @@ import qs.Widgets
// Power Profiles: performance, balanced, eco // Power Profiles: performance, balanced, eco
NBox { NBox {
Layout.fillWidth: true
Layout.preferredWidth: 1 property real spacing: 0
implicitHeight: powerRow.implicitHeight + Style.marginM * 2 * scaling
// Centralized service // Centralized service
readonly property bool hasPP: PowerProfileService.available readonly property bool hasPP: PowerProfileService.available
property real spacing: 0
RowLayout { RowLayout {
id: powerRow id: powerRow
@ -31,8 +29,7 @@ NBox {
tooltipText: "Set performance power profile." tooltipText: "Set performance power profile."
enabled: hasPP enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium opacity: enabled ? Style.opacityFull : Style.opacityMedium
colorBg: (enabled colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
&& PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary
onClicked: { onClicked: {
if (enabled) { if (enabled) {
@ -46,8 +43,7 @@ NBox {
tooltipText: "Set balanced power profile." tooltipText: "Set balanced power profile."
enabled: hasPP enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium opacity: enabled ? Style.opacityFull : Style.opacityMedium
colorBg: (enabled colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
&& PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary
onClicked: { onClicked: {
if (enabled) { if (enabled) {
@ -61,8 +57,7 @@ NBox {
tooltipText: "Set eco power profile." tooltipText: "Set eco power profile."
enabled: hasPP enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium opacity: enabled ? Style.opacityFull : Style.opacityMedium
colorBg: (enabled colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
&& PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary
onClicked: { onClicked: {
if (enabled) { if (enabled) {

View file

@ -16,10 +16,6 @@ NBox {
property string uptimeText: "--" property string uptimeText: "--"
Layout.fillWidth: true
// Height driven by content
implicitHeight: content.implicitHeight + Style.marginM * 2 * scaling
RowLayout { RowLayout {
id: content id: content
anchors.left: parent.left anchors.left: parent.left
@ -63,7 +59,7 @@ NBox {
tooltipText: "Open settings." tooltipText: "Open settings."
onClicked: { onClicked: {
settingsPanel.requestedTab = SettingsPanel.Tab.General settingsPanel.requestedTab = SettingsPanel.Tab.General
settingsPanel.open(screen) settingsPanel.open()
} }
} }
@ -72,7 +68,7 @@ NBox {
icon: "power" icon: "power"
tooltipText: "Power menu." tooltipText: "Power menu."
onClicked: { onClicked: {
powerPanel.open(screen) powerPanel.open()
sidePanel.close() sidePanel.close()
} }
} }

View file

@ -8,9 +8,6 @@ import qs.Widgets
NBox { NBox {
id: root id: root
Layout.preferredWidth: Style.baseWidgetSize * 2.625 * scaling
implicitHeight: content.implicitHeight + Style.marginXS * 2 * scaling
ColumnLayout { ColumnLayout {
id: content id: content
anchors.left: parent.left anchors.left: parent.left

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