Compare commits

..

No commits in common. "7b1a5d2eb2fa2ce058fbc48d735100749d6bb100" and "9792f401f7817349f98f57d2db38e4058efa3288" have entirely different histories.

150 changed files with 5087 additions and 6914 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
View file

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

View file

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

View file

@ -1,34 +0,0 @@
{
"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

@ -1,34 +0,0 @@
{
"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": "#524f67", "mTertiary": "#f6c177",
"mOnTertiary": "#e0def4", "mOnTertiary": "#191724",
"mError": "#eb6f92", "mError": "#eb6f92",
"mOnError": "#191724", "mOnError": "#191724",
"mSurface": "#191724", "mSurface": "#191724",
@ -16,19 +16,19 @@
"mShadow": "#191724" "mShadow": "#191724"
}, },
"light": { "light": {
"mPrimary": "#d7827e", "mPrimary": "#d46e6b",
"mOnPrimary": "#faf4ed", "mOnPrimary": "#faf4ed",
"mSecondary": "#56949f", "mSecondary": "#56949f",
"mOnSecondary": "#faf4ed", "mOnSecondary": "#faf4ed",
"mTertiary": "#cecacd", "mTertiary": "#31748f",
"mOnTertiary": "#575279", "mOnTertiary": "#232136",
"mError": "#b4637a", "mError": "#b4637a",
"mOnError": "#faf4ed", "mOnError": "#f2e9e1",
"mSurface": "#faf4ed", "mSurface": "#e0def4",
"mOnSurface": "#575279", "mOnSurface": "#232136",
"mSurfaceVariant": "#f2e9e1", "mSurfaceVariant": "#bcb8e7",
"mOnSurfaceVariant": "#797593", "mOnSurfaceVariant": "#797593",
"mOutline": "#dfdad9", "mOutline": "#9893a5",
"mShadow": "#faf4ed" "mShadow": "#575279"
} }
} }

View file

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

Binary file not shown.

Binary file not shown.

View file

@ -51,31 +51,28 @@ 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("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\"") 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\"")
} }
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('post_hook = "sed -i /themes/d ~/.config/foot/foot.ini && echo include=~/.config/foot/themes/noctalia >> ~/.config/foot/foot.ini"') lines.push(
'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('post_hook = "sed -i /themes/d ~/.config/fuzzel/fuzzel.ini && echo include=~/.config/fuzzel/themes/noctalia >> ~/.config/fuzzel/fuzzel.ini"') lines.push(
'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

@ -1,22 +0,0 @@
{
"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,113 +1,572 @@
/** /*
* @name noctalia * Vesktop Theme
* @description Original theme: midnight | A dark, rounded discord theme. * Generated with Matugen
* @author refact0r * Base was taken from https://github.com/catppuccin/discord <3
* @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!!! */
@import url('https://refact0r.github.io/midnight-discord/build/midnight.css'); /* Dark Theme */
.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}};
/* customize things here */ /* Text Colors */
:root { --header-primary: {{colors.on_surface.default.hex}} !important;
/* font, change to 'gg sans' for default discord font*/ --header-secondary: {{colors.on_surface_variant.default.hex}} !important;
--font: 'figtree'; --text-normal: {{colors.on_surface.default.hex}} !important;
--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}};
/* top left corner text */ /* Main Background Colors - Bar color (mSurface) colors.surface.default.hex*/
--corner-text: 'Midnight'; --background-primary: {{colors.surface_variant.default.hex}} !important;
--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;
/* color of status indicators and window controls */ /* Secondary Background Colors - Workspace color (mSurfaceVariant) */
--online-indicator: {{colors.inverse_primary.default.hex}}; /* change to #23a55a for default green */ --background-secondary: {{colors.surface.default.hex}} !important;
--dnd-indicator: {{colors.error.default.hex}}; /* change to #f13f43 for default red */ --background-secondary-alt: {{colors.surface.default.hex}} !important;
--idle-indicator: {{colors.tertiary_container.default.hex}}; /* change to #f0b232 for default yellow */ --background-surface-higher: {{colors.surface.default.hex}} !important;
--streaming-indicator: {{colors.on_primary.default.hex}}; /* change to #593695 for default purple */ --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;
/* accent colors */ /* New Messages Banner */
--accent-1: {{colors.tertiary.default.hex}}; /* links */ --background-mentioned: {{colors.primary.default.hex}}15 !important;
--accent-2: {{colors.primary.default.hex}}; /* general unread/mention elements, some icons when active */ --background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
--accent-3: {{colors.primary.default.hex}}; /* accent buttons */ --text-mentioned: {{colors.on_surface.default.hex}} !important;
--accent-4: {{colors.surface_bright.default.hex}}; /* accent buttons when hovered */ --text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
--accent-5: {{colors.primary_fixed_dim.default.hex}}; /* accent buttons when clicked */ --text-mentioned-link: {{colors.primary.default.hex}} !important;
--mention: {{colors.surface.default.hex}}; /* mentions & mention messages */
--mention-hover: {{colors.surface_bright.default.hex}}; /* mentions & mention messages when hovered */
/* text colors */ /* Additional Discord-specific variables for new messages banner */
--text-0: {{colors.surface.default.hex}}; /* text on colored elements */ --background-message-automod: {{colors.primary.default.hex}}15 !important;
--text-1: {{colors.on_surface.default.hex}}; /* other normally white text */ --background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
--text-2: {{colors.on_surface.default.hex}}; /* headings and important text */ --background-message-highlight: {{colors.primary.default.hex}}15 !important;
--text-3: {{colors.on_surface_variant.default.hex}}; /* normal text */ --background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
--text-4: {{colors.on_surface_variant.default.hex}}; /* icon buttons and channels */
--text-5: {{colors.outline.default.hex}}; /* muted channels/chats and timestamps */
/* background and dark colors */ /* Discord unread messages banner specific variables */
--bg-1: {{colors.primary.default.hex}}; /* dark buttons when clicked */ --background-mentioned: {{colors.primary.default.hex}}15 !important;
--bg-2: {{colors.surface_container_high.default.hex}}; /* dark buttons */ --background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
--bg-3: {{colors.surface_container_low.default.hex}}; /* spacing, secondary elements */ --text-mentioned: {{colors.on_surface.default.hex}} !important;
--bg-4: {{colors.surface.default.hex}}; /* main background color */ --text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
--hover: {{colors.surface_bright.default.hex}}; /* channels and buttons when hovered */ --text-mentioned-link: {{colors.primary.default.hex}} !important;
--active: {{colors.surface_bright.default.hex}}; /* channels and buttons when clicked or selected */
--message-hover: {{colors.surface_bright.default.hex}}; /* messages when hovered */
/* amount of spacing and padding */ /* Additional Discord banner text variables */
--spacing: 12px; --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;
/* animations */ /* Additional Discord banner variables */
/* ALL ANIMATIONS CAN BE DISABLED WITH REDUCED MOTION IN DISCORD SETTINGS */ --background-message-automod: {{colors.primary.default.hex}}15 !important;
--list-item-transition: 0.2s ease; /* channels/members/settings hover transition */ --background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
--unread-bar-transition: 0.2s ease; /* unread bar moving into view transition */ --background-message-highlight: {{colors.primary.default.hex}}15 !important;
--moon-spin-transition: 0.4s ease; /* moon icon spin */ --background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
--icon-spin-transition: 1s ease; /* round icon button spin (settings, emoji, etc.) */ --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;
/* corner roundness (border-radius) */ /* Chat Input Improvements */
--roundness-xl: 22px; /* roundness of big panel outer corners */ --text-input-background: {{colors.surface_container.default.hex}} !important;
--roundness-l: 20px; /* popout panels */ --text-input-border: {{colors.outline.default.hex}} !important;
--roundness-m: 16px; /* smaller panels, images, embeds */ --text-input-border-hover: {{colors.primary.default.hex}} !important;
--roundness-s: 12px; /* members, settings inputs */
--roundness-xs: 10px; /* channels, buttons */
--roundness-xxs: 8px; /* searchbar, small elements */
/* direct messages moon icon */ /* Additional Discord-specific input variables */
/* change to block to show, none to hide */ --deprecated-text-input-bg: {{colors.surface_container.default.hex}} !important;
--discord-icon: none; /* discord icon */ --deprecated-text-input-border: {{colors.outline.default.hex}} !important;
--moon-icon: block; /* moon icon */ --deprecated-text-input-border-hover: {{colors.primary.default.hex}} !important;
--moon-icon-url: url('https://upload.wikimedia.org/wikipedia/commons/c/c4/Font_Awesome_5_solid_moon.svg'); /* custom icon url */ --input-background: {{colors.surface_container.default.hex}} !important;
--moon-icon-size: auto; --input-border: {{colors.outline.default.hex}} !important;
--input-placeholder-text: {{colors.on_surface_variant.default.hex}} !important;
/* filter uncolorable elements to fit theme */ /* Elevated/Container Backgrounds */
/* (just set to none, they're too much work to configure) */ --background-tertiary: {{colors.surface_container.default.hex}} !important;
--login-bg-filter: saturate(0.3) hue-rotate(-15deg) brightness(0.4); /* login background artwork */ --background-accent: {{colors.surface_container.default.hex}} !important;
--green-to-accent-3-filter: hue-rotate(56deg) saturate(1.43); /* add friend page explore icon */ --background-surface-highest: {{colors.surface_container_high.default.hex}} !important;
--blurple-to-accent-3-filter: hue-rotate(304deg) saturate(0.84) brightness(1.2); /* add friend page school icon */ --background-base-lowest: {{colors.surface_container.default.hex}} !important;
/* Border Colors */
--border-faint: {{colors.outline_variant.default.hex}};
--border-strong: {{colors.surface_container.default.hex}};
--border-normal: {{colors.surface_container_high.default.hex}};
--border-subtle: {{colors.surface.default.hex}} !important;
--chat-border: {{colors.surface_container_high.default.hex}};
/* Status Colors */
--status-positive: {{colors.tertiary.default.hex}};
--status-positive-background: {{colors.tertiary.default.hex}};
--status-positive-text: {{colors.on_tertiary.default.hex}};
--text-positive: {{colors.tertiary.default.hex}};
--text-feedback-positive: {{colors.tertiary.default.hex}};
--background-feedback-positive: {{colors.tertiary.default.hex}}20;
--info-positive-background: {{colors.tertiary.default.hex}}20;
--info-positive-foreground: {{colors.tertiary.default.hex}};
--info-positive-text: {{colors.on_surface.default.hex}};
--status-warning: {{colors.secondary.default.hex}};
--status-warning-background: {{colors.secondary.default.hex}};
--status-warning-text: {{colors.on_secondary.default.hex}};
--text-warning: {{colors.secondary.default.hex}};
--text-feedback-warning: {{colors.secondary.default.hex}};
--background-feedback-warning: {{colors.secondary.default.hex}}20;
--info-warning-background: {{colors.secondary.default.hex}}20;
--info-warning-foreground: {{colors.secondary.default.hex}};
--info-warning-text: {{colors.on_surface.default.hex}};
--status-danger: {{colors.error.default.hex}};
--status-danger-background: {{colors.error.default.hex}};
--status-danger-text: {{colors.on_error.default.hex}};
--text-danger: {{colors.error.default.hex}};
--text-feedback-critical: {{colors.error.default.hex}};
--background-feedback-critical: {{colors.error.default.hex}}20;
--info-danger-background: {{colors.error.default.hex}}20;
--info-danger-foreground: {{colors.error.default.hex}};
--info-danger-text: {{colors.on_surface.default.hex}};
/* Button Colors */
--button-secondary-background: {{colors.surface_variant.default.hex}} !important;
--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}};
--button-filled-brand-background: {{colors.primary.default.hex}};
--button-filled-brand-background-hover: {{colors.primary.default.hex}};
--button-filled-brand-background-active: {{colors.primary.default.hex}};
/* Input Colors */
--input-background: {{colors.surface_container.default.hex}};
--input-border: {{colors.outline.default.hex}};
--input-placeholder-text: {{colors.on_surface_variant.default.hex}};
/* Scrollbar Colors */
--scrollbar-thin-thumb: {{colors.primary.default.hex}};
--scrollbar-thin-track: transparent;
--scrollbar-auto-thumb: {{colors.primary.default.hex}};
--scrollbar-auto-track: {{colors.surface_container_high.default.hex}};
--scrollbar-auto-scrollbar-color-thumb: {{colors.primary.default.hex}};
--scrollbar-auto-scrollbar-color-track: {{colors.surface_container_high.default.hex}};
/* Icon Colors */
--icon-muted: {{colors.on_surface_variant.default.hex}};
--icon-default: {{colors.on_surface.default.hex}};
--icon-primary: {{colors.on_surface.default.hex}};
--icon-secondary: {{colors.on_surface_variant.default.hex}};
--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;
} }
/* Selected chat/friend text */ .visual-refresh.theme-dark ::selection,
.selected_f5eb4b, .visual-refresh .theme-dark ::selection {
.selected_f6f816 .link_d8bfb3 { background-color: {{colors.primary.default.hex}};
color: var(--text-0) !important;
background: var(--accent-3) !important;
} }
.selected_f6f816 .link_d8bfb3 * { /* Force Discord unread messages banner styling */
color: var(--text-0) !important; .visual-refresh.theme-dark .newMessagesBar__0f481,
fill: var(--text-0) !important; .visual-refresh.theme-dark .barButtonMain__0f481,
.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;
} }
/* Make channel name text less visible (darker) */ .visual-refresh.theme-dark .newMessagesBar__0f481:hover,
.name__2ea32 { .visual-refresh.theme-dark .barButtonMain__0f481:hover,
color: var(--text-5) !important; .visual-refresh.theme-dark .barButtonBase__0f481:hover {
opacity: 0.7 !important; background-color: {{colors.surface_container_high.default.hex}} !important;
} }
/* Make unread channel names brighter */ /* Force Discord chat input styling */
.link__2ea32[aria-label*="unread"] .name__2ea32 { .visual-refresh.theme-dark .channelTextArea-rNsIhG,
color: var(--text-2) !important; .visual-refresh.theme-dark .channelTextArea-rNsIhG *,
opacity: 1 !important; .visual-refresh.theme-dark .scrollableContainer-2NUZem,
font-weight: 600 !important; .visual-refresh.theme-dark [data-slate-editor="true"] {
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.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

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 360 -t 2 -i 2 -w {} \; find . -name "*.qml" -print -exec qmlfmt -e -b 120 -t 2 -i 2 -w {} \;

View file

@ -34,7 +34,8 @@ 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(appId) : DesktopEntries.byId(appId) const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(
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.ttf" readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.woff2"
Component.onCompleted: { Component.onCompleted: {
Logger.log("Icons", "Service started") Logger.log("Icons", "Service started")

View file

@ -20,8 +20,7 @@ Singleton {
"folder-open": "folder-open", "folder-open": "folder-open",
"download": "download", "download": "download",
"toast-notice": "circle-check", "toast-notice": "circle-check",
"toast-warning": "alert-circle", "toast-warning": "exclamation-circle",
"toast-error": "circle-x",
"question-mark": "question-mark", "question-mark": "question-mark",
"search": "search", "search": "search",
"warning": "exclamation-circle", "warning": "exclamation-circle",
@ -40,20 +39,21 @@ Singleton {
"balanced": "scale", "balanced": "scale",
"powersaver": "leaf", "powersaver": "leaf",
"storage": "database", "storage": "database",
"ethernet": "sitemap", "ethernet": "sitemap-filled",
"keyboard": "keyboard", "keyboard": "keyboard",
"shutdown": "power", "shutdown": "power",
"lock": "lock", "lock": "lock-filled",
"logout": "logout", "logout": "logout",
"reboot": "refresh", "reboot": "refresh",
"suspend": "player-pause", "suspend": "player-pause-filled",
"nightlight-on": "moon", "nightlight-on": "moon-filled",
"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,47 +84,41 @@ 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", "weather-sun": "sun-filled",
"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-sun": "cloud-sun", "weather-cloud-lightning": "cloud-bolt",
"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", "settings-network": "sitemap-filled",
"settings-brightness": "brightness-up", "settings-brightness": "brightness-up-filled",
"settings-weather": "cloud-sun", "settings-weather": "cloud-rain",
"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", "bt-device-headphones": "headphones-filled",
"bt-device-mouse": "mouse-2", "bt-device-mouse": "mouse-2",
"bt-device-keyboard": "bluetooth", "bt-device-keyboard": "bluetooth",
"bt-device-phone": "device-mobile", "bt-device-phone": "device-mobile-filled",
"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}",
@ -261,8 +255,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}",
"alpha"//"align-right-2": "\u{feff}", "align-right-2": "\u{feff}",
: "\u{f543}", "alpha": "\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}",
@ -2050,7 +2044,6 @@ 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}",
@ -3094,8 +3087,8 @@ Singleton {
"friends": "\u{eab0}", "friends": "\u{eab0}",
"friends-off": "\u{f136}", "friends-off": "\u{f136}",
"frustum": "\u{fa9f}", "frustum": "\u{fa9f}",
"frustum-plus"//"frustum-off": "\u{fa9d}", "frustum-off": "\u{fa9d}",
: "\u{fa9e}", "frustum-plus": "\u{fa9e}",
"function": "\u{f225}", "function": "\u{f225}",
"function-filled": "\u{fc2b}", "function-filled": "\u{fc2b}",
"function-off": "\u{f3f0}", "function-off": "\u{f3f0}",
@ -3354,13 +3347,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-z"//"hexagon-letter-y-filled": "\u{fe2f}", "hexagon-letter-y-filled": "\u{fe2f}",
: "\u{f47b}", "hexagon-letter-z": "\u{f47b}",
"hexagon-minus"//"hexagon-letter-z-filled": "\u{fe2e}", "hexagon-letter-z-filled": "\u{fe2e}",
: "\u{fc8f}", "hexagon-minus": "\u{fc8f}",
"hexagon-minus-2": "\u{fc8e}", "hexagon-minus-2": "\u{fc8e}",
"hexagon-number-0"//"hexagon-minus-filled": "\u{fe2d}", "hexagon-minus-filled": "\u{fe2d}",
: "\u{f459}", "hexagon-number-0": "\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}",
@ -3383,8 +3376,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}",
"hexagonal-prism"//"hexagon-plus-filled": "\u{fe2c}", "hexagon-plus-filled": "\u{fe2c}",
: "\u{faa5}", "hexagonal-prism": "\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}",
@ -3414,8 +3407,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-hand"//"home-filled": "\u{fe2b}", "home-filled": "\u{fe2b}",
: "\u{f504}", "home-hand": "\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}",
@ -3532,8 +3525,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-off"//"ironing-filled": "\u{fe2a}", "ironing-filled": "\u{fe2a}",
: "\u{f2f7}", "ironing-off": "\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}",
@ -3543,8 +3536,8 @@ Singleton {
"italic": "\u{eb93}", "italic": "\u{eb93}",
"jacket": "\u{f661}", "jacket": "\u{f661}",
"jetpack": "\u{f581}", "jetpack": "\u{f581}",
"jewish-star"//"jetpack-filled": "\u{fe29}", "jetpack-filled": "\u{fe29}",
: "\u{f3ff}", "jewish-star": "\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}",
@ -3558,8 +3551,8 @@ Singleton {
"kering": "\u{efb8}", "kering": "\u{efb8}",
"kerning": "\u{efb8}", "kerning": "\u{efb8}",
"key": "\u{eac7}", "key": "\u{eac7}",
"key-off"//"key-filled": "\u{fe28}", "key-filled": "\u{fe28}",
: "\u{f14b}", "key-off": "\u{f14b}",
"keyboard": "\u{ebd6}", "keyboard": "\u{ebd6}",
"keyboard-filled": "\u{100a2}", "keyboard-filled": "\u{100a2}",
"keyboard-hide": "\u{ec7e}", "keyboard-hide": "\u{ec7e}",
@ -3615,20 +3608,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-align-left"//"layout-2-filled": "\u{fe27}", "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}",
: "\u{eacf}", "layout-align-left": "\u{eacf}",
"layout-align-middle"// "layout-align-left-filled": "\u{fe24}", "layout-align-left-filled": "\u{fe24}",
: "\u{ead0}", "layout-align-middle": "\u{ead0}",
"layout-align-right"//"layout-align-middle-filled": "\u{fe23}", "layout-align-middle-filled": "\u{fe23}",
: "\u{ead1}", "layout-align-right": "\u{ead1}",
"layout-align-top"//"layout-align-right-filled": "\u{fe22}", "layout-align-right-filled": "\u{fe22}",
: "\u{ead2}", "layout-align-top": "\u{ead2}",
"layout-board"//"layout-align-top-filled": "\u{fe21}", "layout-align-top-filled": "\u{fe21}",
: "\u{ef95}", "layout-board": "\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}",
@ -3640,8 +3633,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-collage"// "layout-cards-filled": "\u{fe20}", "layout-cards-filled": "\u{fe20}",
: "\u{f389}", "layout-collage": "\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}",
@ -4122,14 +4115,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-off"//"microphone-filled": "\u{fe0f}", "microphone-filled": "\u{fe0f}",
: "\u{ed16}", "microphone-off": "\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-off"//"microwave-filled": "\u{fe0e}", "microwave-filled": "\u{fe0e}",
: "\u{f264}", "microwave-off": "\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}",
@ -4302,7 +4295,6 @@ 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}",
@ -4362,18 +4354,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-5"//"number-42-small": "\u{fff8}", "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}",
: "\u{edf5}", "number-5": "\u{edf5}",
"number-5-small": "\u{fcfa}", "number-5-small": "\u{fcfa}",
"number-51-small"// "number-50-small": "\u{fff0}", "number-50-small": "\u{fff0}",
: "\u{ffef}", "number-51-small": "\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}",
@ -4813,11 +4805,11 @@ Singleton {
"quote": "\u{efbe}", "quote": "\u{efbe}",
"quote-filled": "\u{1009c}", "quote-filled": "\u{1009c}",
"quote-off": "\u{f188}", "quote-off": "\u{f188}",
"radar"//"quotes": "\u{fb1e}", "quotes": "\u{fb1e}",
: "\u{f017}", "radar": "\u{f017}",
"radar-2": "\u{f016}", "radar-2": "\u{f016}",
"radar-off"//"radar-filled": "\u{fe0d}", "radar-filled": "\u{fe0d}",
: "\u{f41f}", "radar-off": "\u{f41f}",
"radio": "\u{ef2d}", "radio": "\u{ef2d}",
"radio-off": "\u{f420}", "radio-off": "\u{f420}",
"radioactive": "\u{ecc0}", "radioactive": "\u{ecc0}",
@ -4877,12 +4869,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-one-to-many"//"relation-many-to-many-filled": "\u{fe0c}", "relation-many-to-many-filled": "\u{fe0c}",
: "\u{ed80}", "relation-one-to-many": "\u{ed80}",
"relation-one-to-one"//"relation-one-to-many-filled": "\u{fe0b}", "relation-one-to-many-filled": "\u{fe0b}",
: "\u{ed81}", "relation-one-to-one": "\u{ed81}",
"reload"//"relation-one-to-one-filled": "\u{fe0a}", "relation-one-to-one-filled": "\u{fe0a}",
: "\u{f3ae}", "reload": "\u{f3ae}",
"reorder": "\u{fc15}", "reorder": "\u{fc15}",
"repeat": "\u{eb72}", "repeat": "\u{eb72}",
"repeat-off": "\u{f18e}", "repeat-off": "\u{f18e}",
@ -5034,8 +5026,8 @@ Singleton {
"search": "\u{eb1c}", "search": "\u{eb1c}",
"search-off": "\u{f19c}", "search-off": "\u{f19c}",
"section": "\u{eed5}", "section": "\u{eed5}",
"section-sign"//"section-filled": "\u{fe09}", "section-filled": "\u{fe09}",
: "\u{f019}", "section-sign": "\u{f019}",
"seeding": "\u{ed51}", "seeding": "\u{ed51}",
"seeding-filled": "\u{10006}", "seeding-filled": "\u{10006}",
"seeding-off": "\u{f19d}", "seeding-off": "\u{f19d}",
@ -5243,8 +5235,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-off"//"soup-filled": "\u{fe08}", "soup-filled": "\u{fe08}",
: "\u{f42d}", "soup-off": "\u{f42d}",
"source-code": "\u{f4a2}", "source-code": "\u{f4a2}",
"space": "\u{ec0c}", "space": "\u{ec0c}",
"space-off": "\u{f1aa}", "space-off": "\u{f1aa}",
@ -5337,22 +5329,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-b"//"square-letter-a-filled": "\u{fe07}", "square-letter-a-filled": "\u{fe07}",
: "\u{f47d}", "square-letter-b": "\u{f47d}",
"square-letter-c"//"square-letter-b-filled": "\u{fe06}", "square-letter-b-filled": "\u{fe06}",
: "\u{f47e}", "square-letter-c": "\u{f47e}",
"square-letter-d"//"square-letter-c-filled": "\u{fe05}", "square-letter-c-filled": "\u{fe05}",
: "\u{f47f}", "square-letter-d": "\u{f47f}",
"square-letter-e"//"square-letter-d-filled": "\u{fe04}", "square-letter-d-filled": "\u{fe04}",
: "\u{f480}", "square-letter-e": "\u{f480}",
"square-letter-f"//"square-letter-e-filled": "\u{fe03}", "square-letter-e-filled": "\u{fe03}",
: "\u{f481}", "square-letter-f": "\u{f481}",
"square-letter-g"//"square-letter-f-filled": "\u{fe02}", "square-letter-f-filled": "\u{fe02}",
: "\u{f482}", "square-letter-g": "\u{f482}",
"square-letter-h"//"square-letter-g-filled": "\u{fe01}", "square-letter-g-filled": "\u{fe01}",
: "\u{f483}", "square-letter-h": "\u{f483}",
"square-letter-i"//"square-letter-h-filled": "\u{fe00}", "square-letter-h-filled": "\u{fe00}",
: "\u{f484}", "square-letter-i": "\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}",

View file

@ -1,205 +0,0 @@
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,8 +13,11 @@ 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") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/" property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME")
property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/" || Quickshell.env(
"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")
@ -55,7 +58,8 @@ Singleton {
} }
} }
if (!hasValidBarMonitor) { if (!hasValidBarMonitor) {
Logger.warn("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens") Logger.warn("Settings",
"No configured bar monitors found on system, clearing bar monitor list to show on all screens")
adapter.bar.monitors = [] adapter.bar.monitors = []
} else { } else {
@ -134,18 +138,13 @@ 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 !== undefined ? widget.alwaysShowPercentage : adapter.bar.alwaysShowBatteryPercentage widget.alwaysShowPercentage = widget.alwaysShowPercentage
!== 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
@ -175,7 +174,7 @@ Singleton {
} }
} }
// Compare settings, to detect if something has been upgraded // Backup the widget definition before altering
const widgetAfter = JSON.stringify(widget) const widgetAfter = JSON.stringify(widget)
return (widgetAfter !== widgetBefore) return (widgetAfter !== widgetBefore)
} }
@ -259,19 +258,14 @@ Singleton {
JsonAdapter { JsonAdapter {
id: adapter id: adapter
property int settingsVersion: 2 property int settingsVersion: 1
// bar // bar
property JsonObject bar: JsonObject { property JsonObject bar: JsonObject {
property string position: "top" // "top", "bottom", "left", or "right" property string position: "top" // "top" or "bottom"
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
@ -322,9 +316,7 @@ 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
} }
@ -384,7 +376,6 @@ 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: []
} }
@ -400,10 +391,6 @@ 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
@ -450,7 +437,6 @@ 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,9 +35,6 @@ 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
@ -66,9 +63,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: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37 property int barHeight: 36
property int capsuleHeight: (barHeight * 0.73) property int capsuleHeight: (barHeight * 0.73)
property int baseWidgetSize: (barHeight * 0.9) property int baseWidgetSize: 32
property int sliderWidth: 200 property int sliderWidth: 200
// Delays // Delays

View file

@ -43,7 +43,9 @@ 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, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0) property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r,
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: {
@ -227,7 +229,8 @@ 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 * 1.6 : Settings.data.wallpaper.transitionDuration duration: transitionType == "stripes" ? 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,7 +60,6 @@ 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
@ -69,7 +68,9 @@ 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, Style.opacityMedium) : Qt.alpha(Color.mOnSurface, Style.opacityMedium) color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface,
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: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) property color cornerColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
property real cornerRadius: Style.screenRadius * scaling property real cornerRadius: 20 * scaling
property real cornerSize: Style.screenRadius * scaling property real cornerSize: 20 * scaling
Connections { Connections {
target: ScalingService target: ScalingService
@ -46,12 +46,12 @@ Loader {
} }
margins { margins {
// When bar is floating, corners should be at screen edges (no margins) top: ((modelData && Settings.data.bar.monitors.includes(modelData.name))
// When bar is not floating, respect bar margins as before || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "top"
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 && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
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 bottom: ((modelData && Settings.data.bar.monitors.includes(modelData.name))
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.monitors.length === 0)) && Settings.data.bar.position === "bottom"
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 && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
} }
mask: Region {} mask: Region {}

View file

@ -27,222 +27,118 @@ Variants {
} }
} }
active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name)
|| (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: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? screen.height : Math.round(Style.barHeight * scaling) implicitHeight: 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" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right" top: Settings.data.bar.position === "top"
bottom: Settings.data.bar.position === "bottom" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right" bottom: Settings.data.bar.position === "bottom"
left: Settings.data.bar.position === "left" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom" left: true
right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom" right: true
}
// 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 with shadow // Background fill
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 // ------------------------------
Loader { // Left Section - Dynamic Widgets
id: verticalBarLayout Row {
anchors.fill: parent id: leftSection
visible: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" objectName: "leftSection"
sourceComponent: verticalBarComponent
}
// For horizontal bars, use the original three-section layout height: parent.height
Loader { anchors.left: parent.left
id: horizontalBarLayout anchors.leftMargin: Style.marginS * scaling
anchors.fill: parent anchors.verticalCenter: parent.verticalCenter
visible: Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom" spacing: Style.marginS * scaling
sourceComponent: horizontalBarComponent
}
// Main layout components Repeater {
Component { model: Settings.data.bar.widgets.left
id: verticalBarComponent delegate: NWidgetLoader {
Item { widgetId: (modelData.id !== undefined ? modelData.id : "")
anchors.fill: parent widgetProps: {
"screen": root.modelData || null,
// Top section (left widgets) "scaling": ScalingService.getScreenScale(screen),
ColumnLayout { "widgetId": modelData.id,
anchors.horizontalCenter: parent.horizontalCenter "barSection": parent.objectName,
anchors.top: parent.top "sectionWidgetIndex": index,
anchors.topMargin: Style.marginM * root.scaling "sectionWidgetsCount": Settings.data.bar.widgets.left.length
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 { // ------------------------------
id: horizontalBarComponent // Center Section - Dynamic Widgets
Item { Row {
anchors.fill: parent id: centerSection
objectName: "centerSection"
// Left Section height: parent.height
RowLayout { anchors.horizontalCenter: parent.horizontalCenter
id: leftSection anchors.verticalCenter: parent.verticalCenter
objectName: "leftSection" spacing: Style.marginS * scaling
anchors.left: parent.left
anchors.leftMargin: Style.marginS * root.scaling
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater { Repeater {
model: Settings.data.bar.widgets.left model: Settings.data.bar.widgets.center
delegate: NWidgetLoader { delegate: NWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "") widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: { widgetProps: {
"screen": root.modelData || null, "screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen), "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id, "widgetId": modelData.id,
"section": "left", "barSection": parent.objectName,
"sectionWidgetIndex": index, "sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.left.length "sectionWidgetsCount": Settings.data.bar.widgets.center.length
}
Layout.alignment: Qt.AlignVCenter
}
} }
anchors.verticalCenter: parent.verticalCenter
} }
}
}
// Center Section // ------------------------------
RowLayout { // Right Section - Dynamic Widgets
id: centerSection Row {
objectName: "centerSection" id: rightSection
anchors.horizontalCenter: parent.horizontalCenter objectName: "rightSection"
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater { height: parent.height
model: Settings.data.bar.widgets.center anchors.right: bar.right
delegate: NWidgetLoader { anchors.rightMargin: Style.marginS * scaling
widgetId: (modelData.id !== undefined ? modelData.id : "") anchors.verticalCenter: bar.verticalCenter
widgetProps: { spacing: Style.marginS * scaling
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen), Repeater {
"widgetId": modelData.id, model: Settings.data.bar.widgets.right
"section": "center", delegate: NWidgetLoader {
"sectionWidgetIndex": index, widgetId: (modelData.id !== undefined ? modelData.id : "")
"sectionWidgetsCount": Settings.data.bar.widgets.center.length widgetProps: {
} "screen": root.modelData || null,
Layout.alignment: Qt.AlignVCenter "scaling": ScalingService.getScreenScale(screen),
} "widgetId": modelData.id,
"barSection": parent.objectName,
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
} }
}
// Right Section
RowLayout {
id: rightSection
objectName: "rightSection"
anchors.right: parent.right
anchors.rightMargin: Style.marginS * root.scaling
anchors.verticalCenter: parent.verticalCenter 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,7 +31,8 @@ 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, flickable.contentHeight + (Style.marginS * 2 * scaling)) implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9,
flickable.contentHeight + (Style.marginS * 2 * scaling))
visible: false visible: false
color: Color.transparent color: Color.transparent
anchor.item: anchorItem anchor.item: anchorItem
@ -158,7 +159,8 @@ PopupWindow {
NText { NText {
id: text id: text
Layout.fillWidth: true Layout.fillWidth: true
color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant color: (modelData?.enabled
?? 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
@ -178,7 +180,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: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) color: Color.mOnSurface
} }
} }
@ -220,32 +222,9 @@ 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
// Determine submenu opening direction based on bar position and available space // Check if there's enough space on the right
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,19 +8,20 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { RowLayout {
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 section: "" property string barSection: ""
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,85 +37,34 @@ Item {
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() {
try { return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
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 { // Try CompositorService first
// Try CompositorService first const focusedWindow = CompositorService.getFocusedWindow()
const focusedWindow = CompositorService.getFocusedWindow() if (focusedWindow && focusedWindow.appId) {
if (focusedWindow && focusedWindow.appId) { const idValue = focusedWindow.appId
try { const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
const idValue = focusedWindow.appId return AppIcons.iconForAppId(normalizedId.toLowerCase())
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
const iconResult = AppIcons.iconForAppId(normalizedId.toLowerCase())
if (iconResult && iconResult !== "") {
return iconResult
}
} catch (iconError) {
Logger.warn("ActiveWindow", "Error getting icon from CompositorService:", iconError)
}
}
// 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 ""
} }
// Fallback to ToplevelManager
if (ToplevelManager && ToplevelManager.activeToplevel) {
const activeToplevel = ToplevelManager.activeToplevel
if (activeToplevel.appId) {
const idValue2 = activeToplevel.appId
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
return AppIcons.iconForAppId(normalizedId2.toLowerCase())
}
}
return ""
} }
// A hidden text element to safely measure the full title width // A hidden text element to safely measure the full title width
@ -129,31 +79,27 @@ Item {
Rectangle { Rectangle {
id: windowTitleRect id: windowTitleRect
visible: root.visible visible: root.visible
anchors.left: parent.left Layout.preferredWidth: contentLayout.implicitWidth + Style.marginM * 2 * scaling
anchors.verticalCenter: parent.verticalCenter Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
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: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling anchors.rightMargin: Style.marginS * scaling
clip: true clip: true
// Horizontal layout for top/bottom bars
RowLayout { RowLayout {
id: horizontalLayout id: contentLayout
anchors.centerIn: parent anchors.centerIn: parent
spacing: 2 * scaling spacing: Style.marginS * scaling
visible: barPosition === "top" || barPosition === "bottom"
// Window icon // Window icon
Item { Item {
Layout.preferredWidth: Style.baseWidgetSize * 0.5 * scaling Layout.preferredWidth: Style.fontSizeL * scaling * 1.2
Layout.preferredHeight: Style.baseWidgetSize * 0.5 * scaling Layout.preferredHeight: Style.fontSizeL * scaling * 1.2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: getTitle() !== "" && showIcon visible: getTitle() !== "" && showIcon
@ -164,28 +110,16 @@ Item {
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: {
try { if (mouseArea.containsMouse) {
if (mouseArea.containsMouse) { return Math.round(Math.min(fullTitleMetrics.contentWidth, root.maxWidth * scaling))
return Math.round(Math.min(fullTitleMetrics.contentWidth, root.maxWidth * scaling)) } else {
} else { return Math.round(Math.min(fullTitleMetrics.contentWidth, root.minWidth * scaling))
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
@ -207,65 +141,12 @@ Item {
} }
} }
// 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
} }
} }
} }
@ -273,20 +154,10 @@ Item {
Connections { Connections {
target: CompositorService target: CompositorService
function onActiveWindowChanged() { function onActiveWindowChanged() {
try { windowIcon.source = Qt.binding(getAppIcon)
windowIcon.source = Qt.binding(getAppIcon)
windowIconVertical.source = Qt.binding(getAppIcon)
} catch (e) {
Logger.warn("ActiveWindow", "Error in onActiveWindowChanged:", e)
}
} }
function onWindowListChanged() { function onWindowListChanged() {
try { windowIcon.source = Qt.binding(getAppIcon)
windowIcon.source = Qt.binding(getAppIcon)
windowIconVertical.source = Qt.binding(getAppIcon)
} catch (e) {
Logger.warn("ActiveWindow", "Error in onWindowListChanged:", e)
}
} }
} }
} }

View file

@ -14,12 +14,13 @@ 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 section: "" property string barSection: ""
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,18 +30,21 @@ Item {
return {} return {}
} }
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" // Resolve settings: try user settings or defaults from BarWidgetRegistry
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode readonly property bool alwaysShowPercentage: widgetSettings.alwaysShowPercentage
readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold !== undefined ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
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: 100 readonly property int testPercent: 90
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 && battery.isPresent) readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery
&& 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
@ -85,12 +89,11 @@ Item {
id: pill id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady) icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent,
text: (isReady || testMode) ? Math.round(percent) : "-" charging, isReady)
suffix: "%" text: (isReady || testMode) ? Math.round(percent) + "%" : "-"
autoHide: false autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow" forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage
forceClose: displayMode === "alwaysHide"
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery)) disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
tooltipText: { tooltipText: {
let lines = [] let lines = []
@ -110,7 +113,8 @@ 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(2) + " W.") lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed(
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,6 +21,5 @@ 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(this) onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(screen, this)
onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
} }

View file

@ -13,12 +13,13 @@ 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 section: "" property string barSection: ""
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,8 +29,8 @@ Item {
return {} return {}
} }
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode !== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
// 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
@ -81,16 +82,15 @@ 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) + "%") : ""
} }
suffix: text.length > 0 ? "%" : "-" forceOpen: userAlwaysShowPercentage
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) + "%\nRight click for settings.\nScroll to modify brightness." return "Brightness: " + Math.round(monitor.brightness * 100) + "%\nMethod: " + monitor.method
+ "\nLeft click for advanced settings.\nScroll up/down to change brightness."
} }
onWheel: function (angle) { onWheel: function (angle) {
@ -106,14 +106,8 @@ Item {
onClicked: { onClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
settingsPanel.open() settingsPanel.open(screen)
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display
settingsPanel.open()
} }
} }
} }

View file

@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Services import qs.Services
@ -13,12 +12,13 @@ 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 section: "" property string barSection: ""
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,178 +28,45 @@ Rectangle {
return {} return {}
} }
readonly property string barPosition: Settings.data.bar.position
// Resolve settings: try user settings or defaults from BarWidgetRegistry // Resolve settings: try user settings or defaults from BarWidgetRegistry
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 use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth !== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth readonly property bool showSeconds: widgetSettings.showSeconds !== undefined ? widgetSettings.showSeconds : widgetMetadata.showSeconds
readonly property string displayFormat: widgetSettings.displayFormat !== undefined ? widgetSettings.displayFormat : widgetMetadata.displayFormat readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth
!== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
// Use compact mode for vertical bars implicitWidth: clock.width + Style.marginM * 2 * scaling
readonly property bool verticalMode: barPosition === "left" || barPosition === "right" implicitHeight: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
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
Item { // Clock Icon with attached calendar
id: clockContainer NText {
anchors.fill: parent id: clock
anchors.margins: Style.marginXS * scaling text: {
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)
ColumnLayout { if (showDate) {
id: layout let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
anchors.centerIn: parent dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
spacing: verticalMode ? -2 * scaling : -3 * scaling let day = now.getDate()
let month = now.toLocaleDateString(Qt.locale(), "MMM")
// Compact mode for vertical bars - Time section (HH, MM) return timeString + " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
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: clockContainer target: clock
positionAbove: Settings.data.bar.position === "bottom" positionAbove: Settings.data.bar.position === "bottom"
} }
@ -218,7 +85,7 @@ Rectangle {
} }
onClicked: { onClicked: {
tooltip.hide() tooltip.hide()
PanelService.getPanel("calendarPanel")?.toggle(this) PanelService.getPanel("calendarPanel")?.toggle(screen, this)
} }
} }
} }

View file

@ -1,13 +1,12 @@
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
Item { NIconButton {
id: root id: root
// Widget properties passed from Bar.qml // Widget properties passed from Bar.qml
@ -16,12 +15,13 @@ 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 section: "" property string barSection: ""
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,80 +36,33 @@ Item {
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)
implicitWidth: pill.width enabled: hasExec
implicitHeight: pill.height allowClickWhenDisabled: true // we want to be able to open config with left click when its not setup properly
colorBorder: Color.transparent
NPill { colorBorderHover: Color.transparent
id: pill sizeRatio: 0.8
icon: customIcon
rightOpen: BarWidgetRegistry.getNPillDirection(root) tooltipText: {
icon: customIcon if (!hasExec) {
text: _dynamicText return "Custom Button - Configure in settings"
autoHide: false } else {
forceOpen: _dynamicText !== "" var lines = []
forceClose: false if (leftClickExec !== "") {
disableOpen: true lines.push(`Left click: <i>${leftClickExec}</i>.`)
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() }
onRightClicked: root.onRightClicked() if (middleClickExec !== "") {
onMiddleClicked: root.onMiddleClicked() lines.push(`Middle click: <i>${middleClickExec}</i>.`)
} }
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
} }
} }
Process { onClicked: {
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}`)
@ -117,18 +70,18 @@ Item {
// 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() settingsPanel.open(screen)
} }
} }
function onRightClicked() { 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}`)
} }
} }
function onMiddleClicked() { 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,5 +17,7 @@ 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,25 +13,6 @@ 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
@ -45,10 +26,9 @@ 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.toUpperCase() text: currentLayout
tooltipText: "Keyboard layout: " + currentLayout.toUpperCase() tooltipText: "Keyboard layout: " + currentLayout
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
Item { RowLayout {
id: root id: root
property ShellScreen screen property ShellScreen screen
@ -15,12 +15,13 @@ 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 section: "" property string barSection: ""
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,11 +31,12 @@ Item {
return {} return {}
} }
readonly property string barPosition: Settings.data.bar.position readonly property bool showAlbumArt: (widgetSettings.showAlbumArt
!== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
readonly property bool showAlbumArt: (widgetSettings.showAlbumArt !== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt readonly property bool showVisualizer: (widgetSettings.showVisualizer
readonly property bool showVisualizer: (widgetSettings.showVisualizer !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.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)
@ -44,26 +46,10 @@ Item {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
} }
function calculatedVerticalHeight() { Layout.alignment: Qt.AlignVCenter
return Math.round(Style.baseWidgetSize * 0.8 * scaling) spacing: Style.marginS * 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 {
@ -75,12 +61,12 @@ Item {
Rectangle { Rectangle {
id: mediaMini id: mediaMini
visible: root.visible
anchors.left: parent.left Layout.preferredWidth: rowLayout.implicitWidth + Style.marginM * 2 * scaling
anchors.verticalCenter: parent.verticalCenter Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (rowLayout.implicitWidth + Style.marginM * 2 * scaling) Layout.alignment: Qt.AlignVCenter
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling) radius: 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
@ -93,8 +79,8 @@ Item {
Item { Item {
id: mainContainer id: mainContainer
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling anchors.rightMargin: Style.marginS * scaling
Loader { Loader {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -141,12 +127,10 @@ Item {
} }
} }
// 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 {
@ -207,33 +191,6 @@ Item {
} }
} }
// 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
@ -256,18 +213,12 @@ Item {
} }
onEntered: { onEntered: {
if (barPosition === "left" || barPosition === "right") { if (tooltip.text !== "") {
tooltip.show()
} else if (tooltip.text !== "") {
tooltip.show() tooltip.show()
} }
} }
onExited: { onExited: {
if (barPosition === "left" || barPosition === "right") { tooltip.hide()
tooltip.hide()
} else {
tooltip.hide()
}
} }
} }
} }
@ -276,23 +227,16 @@ Item {
NTooltip { NTooltip {
id: tooltip id: tooltip
text: { text: {
if (barPosition === "left" || barPosition === "right") { var str = ""
return getTitle() if (MediaService.canGoNext) {
} else { str += "Right click for next.\n"
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: (barPosition === "left" || barPosition === "right") ? verticalLayout : anchor target: anchor
positionLeft: barPosition === "right"
positionRight: barPosition === "left"
positionAbove: Settings.data.bar.position === "bottom" positionAbove: Settings.data.bar.position === "bottom"
delay: 500
} }
} }

View file

@ -15,12 +15,13 @@ 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 section: "" property string barSection: ""
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,8 +31,8 @@ Item {
return {} return {}
} }
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
// 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
@ -88,14 +89,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) + "%"
suffix: "%" forceOpen: alwaysShowPercentage
forceOpen: displayMode === "alwaysShow" tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100)
forceClose: displayMode === "alwaysHide" + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
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
@ -108,12 +109,12 @@ Item {
} }
} }
onClicked: { onClicked: {
AudioService.setInputMuted(!AudioService.inputMuted)
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Audio settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open() settingsPanel.open(screen)
}
onRightClicked: {
AudioService.setInputMuted(!AudioService.inputMuted)
} }
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.forced ? Color.mPrimary : Color.mSurfaceVariant colorBg: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? Color.mTertiary : Color.mPrimary) : Color.mSurfaceVariant
colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mOnSurface colorFg: Settings.data.nightLight.enabled ? 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.Display settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
settingsPanel.open() settingsPanel.open(screen)
} }
} }

View file

@ -15,12 +15,13 @@ 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 section: "" property string barSection: ""
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 +30,10 @@ NIconButton {
} }
return {} return {}
} }
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
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
@ -59,7 +62,7 @@ NIconButton {
onClicked: { onClicked: {
var panel = PanelService.getPanel("notificationHistoryPanel") var panel = PanelService.getPanel("notificationHistoryPanel")
panel?.toggle(this) panel?.toggle(screen, 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() onClicked: PanelService.getPanel("powerPanel")?.toggle(screen)
} }

View file

@ -16,5 +16,6 @@ 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,12 +14,13 @@ 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 section: "" property string barSection: ""
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,27 +30,39 @@ NIconButton {
return {} return {}
} }
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo readonly property bool useDistroLogo: (widgetSettings.useDistroLogo
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
icon: useDistroLogo ? "" : "noctalia" icon: useDistroLogo ? "" : "apps"
tooltipText: "Open side panel." tooltipText: "Open side panel."
sizeRatio: 0.85 sizeRatio: 0.8
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: useDistroLogo ? Color.mTertiary : Color.transparent colorBorderHover: Color.transparent
onClicked: PanelService.getPanel("sidePanel")?.toggle(this)
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle() anchors.verticalCenter: parent.verticalCenter
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.85 width: root.width * 0.6
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,12 +14,13 @@ 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 section: "" property string barSection: ""
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
Rectangle { RowLayout {
id: root id: root
property ShellScreen screen property ShellScreen screen
@ -13,12 +13,13 @@ 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 section: "" property string barSection: ""
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,419 +29,245 @@ Rectangle {
return {} return {}
} }
readonly property string barPosition: Settings.data.bar.position readonly property bool showCpuUsage: (widgetSettings.showCpuUsage
!== 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 !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
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)
anchors.centerIn: parent Layout.alignment: Qt.AlignVCenter
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(horizontalLayout.implicitWidth + Style.marginM * 2 * scaling) spacing: Style.marginS * 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
// Compact speed formatter for vertical bar display Rectangle {
function formatCompactSpeed(bytesPerSecond) { Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
if (!bytesPerSecond || bytesPerSecond <= 0) Layout.preferredWidth: mainLayout.implicitWidth + Style.marginM * scaling * 2
return "0" Layout.alignment: Qt.AlignVCenter
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]
}
// Horizontal layout for top/bottom bars radius: Math.round(Style.radiusM * scaling)
RowLayout { color: Color.mSurfaceVariant
id: horizontalLayout
anchors.centerIn: parent
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
spacing: Style.marginXS * scaling
visible: barPosition === "top" || barPosition === "bottom"
// CPU Usage Component RowLayout {
Item { id: mainLayout
Layout.preferredWidth: cpuUsageRow.implicitWidth anchors.centerIn: parent // Better centering than margins
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) width: parent.width - Style.marginM * scaling * 2
Layout.alignment: Qt.AlignVCenter spacing: Style.marginS * scaling
visible: showCpuUsage
RowLayout { // CPU Usage Component
id: cpuUsageRow Item {
anchors.centerIn: parent Layout.preferredWidth: cpuUsageRow.implicitWidth
spacing: Style.marginXXS * scaling Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage
NIcon { RowLayout {
icon: "cpu-usage" id: cpuUsageRow
font.pointSize: Style.fontSizeM * scaling anchors.centerIn: parent
Layout.alignment: Qt.AlignVCenter spacing: Style.marginXS * scaling
}
NText { NIcon {
text: `${SystemStatService.cpuUsage}%` icon: "cpu-usage"
font.family: Settings.data.ui.fontFixed font.pointSize: Style.fontSizeM * scaling
font.pointSize: Style.fontSizeXS * scaling Layout.alignment: Qt.AlignVCenter
font.weight: Style.fontWeightMedium }
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter NText {
color: Color.mPrimary text: `${SystemStatService.cpuUsage}%`
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.marginXXS * scaling spacing: Style.marginXS * 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
}
} }
} }
}
// Memory Usage Component // GPU Temperature Component
Item { Item {
Layout.preferredWidth: memoryUsageRow.implicitWidth Layout.preferredWidth: gpuTempRow.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: showGpuTemp
RowLayout { RowLayout {
id: memoryUsageRow id: gpuTempRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXXS * scaling spacing: Style.marginXS * scaling
NIcon { NIcon {
icon: "memory" icon: "gpu-temperature"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NText { NText {
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G` text: `${SystemStatService.gpuTemp}°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
}
} }
} }
}
// Network Download Speed Component // Memory Usage Component
Item { Item {
Layout.preferredWidth: networkDownloadRow.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: showNetworkStats visible: showMemoryUsage
RowLayout { RowLayout {
id: networkDownloadRow id: memoryUsageRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
NIcon { NIcon {
icon: "download-speed" icon: "memory"
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: 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
}
} }
} }
}
// Network Upload Speed Component // Network Download Speed Component
Item { Item {
Layout.preferredWidth: networkUploadRow.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: showNetworkStats visible: showNetworkStats
RowLayout { RowLayout {
id: networkUploadRow id: networkDownloadRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
NIcon { NIcon {
icon: "upload-speed" icon: "download-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.txSpeed) 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
}
} }
} }
}
// Disk Usage Component (primary drive) // Network Upload Speed Component
Item { Item {
Layout.preferredWidth: diskUsageRow.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: showDiskUsage visible: showNetworkStats
RowLayout { RowLayout {
id: diskUsageRow id: networkUploadRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
NIcon { NIcon {
icon: "storage" 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.diskPercent}%` 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
}
} }
} }
}
}
// Vertical layout for left/right bars // Disk Usage Component (primary drive)
ColumnLayout { Item {
id: verticalLayout Layout.preferredWidth: diskUsageRow.implicitWidth
anchors.centerIn: parent Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
anchors.topMargin: Style.marginS * scaling Layout.alignment: Qt.AlignVCenter
anchors.bottomMargin: Style.marginS * scaling visible: showDiskUsage
width: Math.round(28 * scaling)
spacing: Style.marginS * scaling
visible: barPosition === "left" || barPosition === "right"
// CPU Usage Component RowLayout {
Item { id: diskUsageRow
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling) anchors.centerIn: parent
Layout.preferredWidth: Math.round(28 * scaling) spacing: Style.marginXS * scaling
Layout.alignment: Qt.AlignHCenter
visible: showCpuUsage
Column { NIcon {
id: cpuUsageRowVertical icon: "storage"
anchors.centerIn: parent font.pointSize: Style.fontSizeM * scaling
spacing: Style.marginXXS * scaling Layout.alignment: Qt.AlignVCenter
}
NText { NText {
text: `${Math.round(SystemStatService.cpuUsage)}%` text: `${SystemStatService.diskPercent}%`
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
anchors.horizontalCenter: parent.horizontalCenter Layout.alignment: Qt.AlignVCenter
horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter
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,37 +17,36 @@ 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 set the screen for the trayMenu // When the widget is fully initialized with its props
// 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: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2) implicitWidth: trayLayout.implicitWidth + Style.marginM * scaling * 2
implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling) implicitHeight: 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
Flow { RowLayout {
id: trayFlow id: trayLayout
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 {
width: itemSize Layout.preferredWidth: itemSize
height: itemSize Layout.preferredHeight: itemSize
Layout.alignment: Qt.AlignCenter
visible: modelData visible: modelData
IconImage { IconImage {
@ -112,21 +111,9 @@ Rectangle {
if (modelData.hasMenu && modelData.menu && trayMenu.item) { if (modelData.hasMenu && modelData.menu && trayMenu.item) {
trayPanel.open() trayPanel.open()
// Position menu based on bar position // Anchor the menu to the tray icon item (parent) and position it below the icon
let menuX, menuY const menuX = (width / 2) - (trayMenu.item.width / 2)
if (barPosition === "left") { const menuY = Math.round(Style.barHeight * scaling)
// 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,12 +15,13 @@ 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 section: "" property string barSection: ""
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,8 +31,8 @@ Item {
return {} return {}
} }
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
// 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
@ -77,11 +78,10 @@ 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) + "%"
suffix: "%" forceOpen: alwaysShowPercentage
forceOpen: displayMode === "alwaysShow" tooltipText: "Volume: " + Math.round(AudioService.volume * 100)
forceClose: displayMode === "alwaysHide" + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
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: {
AudioService.setOutputMuted(!AudioService.muted)
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Audio settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open() settingsPanel.open(screen)
}
onRightClicked: {
AudioService.setMuted(!AudioService.muted)
} }
onMiddleClicked: { onMiddleClicked: {
Quickshell.execDetached(["pwvucontrol"]) Quickshell.execDetached(["pwvucontrol"])

View file

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

View file

@ -16,12 +16,13 @@ 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 section: "" property string barSection: ""
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,10 +32,7 @@ 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
@ -49,39 +47,8 @@ Item {
signal workspaceChanged(int workspaceId, color accentColor) signal workspaceChanged(int workspaceId, color accentColor)
implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling) implicitHeight: Math.round(Style.barHeight * scaling)
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.barHeight * scaling) : calculatedHorizontalWidth() implicitWidth: {
function calculatedWsWidth(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 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 let total = 0
for (var i = 0; i < localWorkspaces.count; i++) { for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i) const ws = localWorkspaces.get(i)
@ -92,6 +59,15 @@ Item {
return total return total
} }
function calculatedWsWidth(ws) {
if (ws.isFocused)
return Math.round(44 * scaling)
else if (ws.isActive)
return Math.round(28 * scaling)
else
return Math.round(20 * scaling)
}
Component.onCompleted: { Component.onCompleted: {
refreshWorkspaces() refreshWorkspaces()
} }
@ -101,7 +77,6 @@ Item {
} }
onScreenChanged: refreshWorkspaces() onScreenChanged: refreshWorkspaces()
onHideUnoccupiedChanged: refreshWorkspaces()
Connections { Connections {
target: WorkspaceService target: WorkspaceService
@ -116,15 +91,11 @@ 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)
} }
} }
} }
workspaceRepeaterHorizontal.model = localWorkspaces workspaceRepeater.model = localWorkspaces
workspaceRepeaterVertical.model = localWorkspaces
updateWorkspaceFocus() updateWorkspaceFocus()
} }
@ -173,8 +144,9 @@ Item {
Rectangle { Rectangle {
id: workspaceBackground id: workspaceBackground
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : parent.width width: 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
@ -182,17 +154,14 @@ 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: workspaceRepeaterHorizontal id: workspaceRepeater
model: localWorkspaces model: localWorkspaces
Item { Item {
id: workspacePillContainer id: workspacePillContainer
@ -228,6 +197,8 @@ 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
} }
@ -243,6 +214,8 @@ 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
} }
@ -326,149 +299,4 @@ 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,7 +116,8 @@ ColumnLayout {
NText { NText {
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : "" text: (modelData.signalStrength !== undefined
&& 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,9 +11,8 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
preferredWidth: 380 panelWidth: 380 * scaling
preferredHeight: 500 panelHeight: 500 * scaling
panelKeyboardFocus: true
panelContent: Rectangle { panelContent: Rectangle {
color: Color.transparent color: Color.transparent
@ -43,7 +42,7 @@ NPanel {
} }
NToggle { NToggle {
id: bluetoothSwitch id: wifiSwitch
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
@ -63,7 +62,7 @@ NPanel {
NIconButton { NIconButton {
icon: "close" icon: "close"
tooltipText: "Close." tooltipText: "Close"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: {
root.close() root.close()
@ -76,7 +75,7 @@ NPanel {
} }
Rectangle { Rectangle {
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled) visible: !Settings.data.network.bluetoothEnabled
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
color: Color.transparent color: Color.transparent
@ -109,12 +108,12 @@ NPanel {
} }
} }
NScrollView { ScrollView {
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded ScrollBar.vertical.policy: ScrollBar.AsNeeded
clip: true clip: true
contentWidth: availableWidth contentWidth: availableWidth
@ -143,7 +142,8 @@ 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 && (dev.paired || dev.trusted)) var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected
&& (dev.paired || dev.trusted))
return BluetoothService.sortDevices(filtered) return BluetoothService.sortDevices(filtered)
} }
model: items model: items
@ -175,7 +175,10 @@ NPanel {
} }
var availableCount = Bluetooth.devices.values.filter(dev => { var availableCount = Bluetooth.devices.values.filter(dev => {
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0) return dev && !dev.paired && !dev.pairing
&& !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
preferredWidth: 340 panelWidth: 340 * scaling
preferredHeight: 320 panelHeight: 320 * scaling
panelAnchorRight: Settings.data.bar.position === "right" panelAnchorRight: true
// 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: Item { delegate: Loader {
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,368 +25,318 @@ Variants {
} }
} }
// Shared properties between peek and dock windows active: Settings.isLoaded && modelData ? Settings.data.dock.monitors.includes(modelData.name) : false
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
// Bar detection and positioning properties sourceComponent: PanelWindow {
readonly property bool hasBar: modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false id: dockWindow
readonly property bool barAtBottom: hasBar && Settings.data.bar.position === "bottom"
readonly property int barHeight: Style.barHeight * scaling
// Shared state between windows screen: modelData
property bool dockHovered: false
property bool anyAppHovered: false
property bool hidden: autoHide
property bool peekHovered: false
// Separate property to control Loader - stays true during animations WlrLayershell.namespace: "noctalia-dock"
property bool dockLoaded: !autoHide // Start loaded if autoHide is off
// Timer to unload dock after hide animation completes readonly property bool autoHide: Settings.data.dock.autoHide
Timer { readonly property int hideDelay: 500
id: unloadTimer readonly property int showDelay: 100
interval: hideAnimationDuration + 50 // Add small buffer readonly property int hideAnimationDuration: Style.animationFast
onTriggered: { readonly property int showAnimationDuration: Style.animationFast
if (hidden && autoHide) { readonly property int peekHeight: 7 * scaling
dockLoaded = false readonly property int fullHeight: dockContainer.height
} readonly property int iconSize: 36 * scaling
} readonly property int floatingMargin: 12 * scaling // Margin to make dock float
}
// Timer for auto-hide delay // Bar detection and positioning properties
Timer { readonly property bool hasBar: modelData.name ? (Settings.data.bar.monitors.includes(modelData.name)
id: hideTimer || (Settings.data.bar.monitors.length === 0)) : false
interval: hideDelay readonly property bool barAtBottom: hasBar && Settings.data.bar.position === "bottom"
onTriggered: { readonly property bool barAtTop: hasBar && Settings.data.bar.position === "top"
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered) { readonly property int barHeight: (barAtBottom || barAtTop) ? (Settings.data.bar.height || 30) * scaling : 0
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 show delay // Timer for auto-hide delay
Timer { Timer {
id: showTimer id: hideTimer
interval: showDelay interval: hideDelay
onTriggered: { onTriggered: {
if (autoHide) { if (autoHide && !dockHovered && !anyAppHovered && !peekArea.containsMouse) {
dockLoaded = true // Load dock immediately hidden = true
hidden = false // Then trigger show animation }
unloadTimer.stop() // Cancel any pending unload
} }
} }
}
// Watch for autoHide setting changes // Timer for show delay
onAutoHideChanged: { Timer {
if (!autoHide) { id: showTimer
hidden = false interval: showDelay
dockLoaded = true onTriggered: {
hideTimer.stop() if (autoHide) {
showTimer.stop() hidden = false
unloadTimer.stop() }
} else { }
hidden = true
unloadTimer.restart() // Schedule unload after animation
} }
}
// PEEK WINDOW - Always visible when auto-hide is enabled // Peek area that remains visible when dock is hidden
Loader { MouseArea {
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && autoHide id: peekArea
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: peekHeight + floatingMargin + (barAtBottom ? dockSpacing : 0)
hoverEnabled: autoHide
visible: autoHide
sourceComponent: PanelWindow { onEntered: {
id: peekWindow if (autoHide && hidden) {
showTimer.start()
}
}
screen: modelData onExited: {
anchors.bottom: true if (autoHide && !hidden && !dockHovered && !anyAppHovered) {
anchors.left: true hideTimer.restart()
anchors.right: true }
focusable: false }
color: Color.transparent }
WlrLayershell.namespace: "noctalia-dock-peek" Rectangle {
WlrLayershell.exclusionMode: ExclusionMode.Auto // Always exclusive id: dockContainer
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
implicitHeight: peekHeight // Fade and zoom animation properties
opacity: hidden ? 0 : 1
scale: hidden ? 0.85 : 1
Rectangle { Behavior on opacity {
anchors.fill: parent NumberAnimation {
color: barAtBottom ? Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) : Color.transparent duration: hidden ? hideAnimationDuration : showAnimationDuration
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: peekArea id: dockMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onEntered: { onEntered: {
peekHovered = true dockHovered = true
if (hidden) { if (autoHide) {
showTimer.start() showTimer.stop()
hideTimer.stop()
if (hidden) {
hidden = false
}
} }
} }
onExited: { onExited: {
peekHovered = false dockHovered = false
if (!hidden && !dockHovered && !anyAppHovered) { // Only start hide timer if we're not hovering over any app or the peek area
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: dockContainerWrapper id: dock
width: dockContainer.width width: dockLayout.implicitWidth
height: dockContainer.height height: parent.height - (Style.marginM * 2 * scaling)
anchors.horizontalCenter: parent.horizontalCenter anchors.centerIn: parent
anchors.bottom: parent.bottom
// Apply animations to this wrapper function getAppIcon(toplevel: Toplevel): string {
opacity: hidden ? 0 : 1 if (!toplevel)
scale: hidden ? 0.85 : 1 return ""
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
Behavior on opacity {
NumberAnimation {
duration: hidden ? hideAnimationDuration : showAnimationDuration
easing.type: Easing.InOutQuad
}
} }
Behavior on scale { RowLayout {
NumberAnimation { id: dockLayout
duration: hidden ? hideAnimationDuration : showAnimationDuration spacing: Style.marginL * scaling
easing.type: hidden ? Easing.InQuad : Easing.OutBack Layout.preferredHeight: parent.height
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)
MouseArea { Repeater {
id: dockMouseArea model: ToplevelManager ? ToplevelManager.toplevels : null
anchors.fill: parent
hoverEnabled: true
onEntered: { delegate: Item {
dockHovered = true id: appButton
if (autoHide) { Layout.preferredWidth: iconSize
showTimer.stop() Layout.preferredHeight: iconSize
hideTimer.stop() Layout.alignment: Qt.AlignCenter
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
} }
}
onExited: { // The icon with better quality settings
dockHovered = false Image {
if (autoHide && !anyAppHovered && !peekHovered) { id: appIcon
hideTimer.restart() width: iconSize
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
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
easing.overshoot: 1.2
}
}
} }
}
}
Item { // Fall back if no icon
id: dock NIcon {
width: dockLayout.implicitWidth anchors.centerIn: parent
height: parent.height - (Style.marginM * 2 * scaling) visible: !appIcon.visible
anchors.centerIn: parent icon: "question-mark"
font.pointSize: iconSize * 0.7
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
scale: appButton.hovered ? 1.15 : 1.0
function getAppIcon(toplevel: Toplevel): string { Behavior on scale {
if (!toplevel) NumberAnimation {
return "" duration: Style.animationFast
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase()) easing.type: Easing.OutBack
} 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
} }
}
}
Image { MouseArea {
id: appIcon id: appMouseArea
width: iconSize anchors.fill: parent
height: iconSize hoverEnabled: true
anchors.centerIn: parent cursorShape: Qt.PointingHandCursor
source: dock.getAppIcon(modelData) acceptedButtons: Qt.LeftButton | Qt.MiddleButton
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 onEntered: {
anyAppHovered = true
Behavior on scale { const appName = appButton.appTitle || appButton.appId || "Unknown"
NumberAnimation { appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
duration: Style.animationNormal appTooltip.isVisible = true
easing.type: Easing.OutBack if (autoHide) {
easing.overshoot: 1.2 showTimer.stop()
} hideTimer.stop()
if (hidden) {
hidden = false
} }
} }
}
// Fall back if no icon onExited: {
NIcon { anyAppHovered = false
anchors.centerIn: parent appTooltip.hide()
visible: !appIcon.visible // Only start hide timer if we're not hovering over the dock or peek area
icon: "question-mark" if (autoHide && !dockHovered && !peekArea.containsMouse) {
font.pointSize: iconSize * 0.7 hideTimer.restart()
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
scale: appButton.hovered ? 1.15 : 1.0
Behavior on scale {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.OutBack
easing.overshoot: 1.2
}
}
} }
}
MouseArea { onClicked: function (mouse) {
id: appMouseArea if (mouse.button === Qt.MiddleButton && modelData?.close) {
anchors.fill: parent modelData.close()
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
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()
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()
}
}
} }
if (mouse.button === Qt.LeftButton && modelData?.activate) {
modelData.activate()
}
}
}
// Active indicator // Active indicator
Rectangle { Rectangle {
visible: isActive visible: isActive
width: iconSize * 0.2 width: iconSize * 0.2
height: iconSize * 0.1 height: iconSize * 0.1
color: Color.mPrimary color: Color.mPrimary
radius: Style.radiusXS * scaling radius: Style.radiusXS * scaling
anchors.top: parent.bottom anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: Style.marginXXS * 1.5 * scaling
// Pulse animation for active indicator // Pulse animation for active indicator
SequentialAnimation on opacity { SequentialAnimation on opacity {
running: isActive running: isActive
loops: Animation.Infinite loops: Animation.Infinite
NumberAnimation { NumberAnimation {
to: 0.6 to: 0.6
duration: Style.animationSlowest duration: Style.animationSlowest
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
} }
NumberAnimation { NumberAnimation {
to: 1.0 to: 1.0
duration: Style.animationSlowest duration: Style.animationSlowest
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
}
}
} }
} }
} }

View file

@ -8,6 +8,17 @@ 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() {
@ -20,14 +31,14 @@ Item {
IpcHandler { IpcHandler {
target: "settings" target: "settings"
function toggle() { function toggle() {
settingsPanel.toggle() settingsPanel.toggle(getActiveScreen())
} }
} }
IpcHandler { IpcHandler {
target: "notifications" target: "notifications"
function toggleHistory() { function toggleHistory() {
notificationHistoryPanel.toggle() notificationHistoryPanel.toggle(getActiveScreen())
} }
function toggleDND() { function toggleDND() {
Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
@ -44,15 +55,15 @@ Item {
IpcHandler { IpcHandler {
target: "launcher" target: "launcher"
function toggle() { function toggle() {
launcherPanel.toggle() launcherPanel.toggle(getActiveScreen())
} }
function clipboard() { function clipboard() {
launcherPanel.setSearchText(">clip ") launcherPanel.setSearchText(">clip ")
launcherPanel.toggle() launcherPanel.toggle(getActiveScreen())
} }
function calculator() { function calculator() {
launcherPanel.setSearchText(">calc ") launcherPanel.setSearchText(">calc ")
launcherPanel.toggle() launcherPanel.toggle(getActiveScreen())
} }
} }
@ -99,7 +110,7 @@ Item {
AudioService.decreaseVolume() AudioService.decreaseVolume()
} }
function muteOutput() { function muteOutput() {
AudioService.setOutputMuted(!AudioService.muted) AudioService.setMuted(!AudioService.muted)
} }
function muteInput() { function muteInput() {
if (AudioService.source?.ready && AudioService.source?.audio) { if (AudioService.source?.ready && AudioService.source?.audio) {
@ -111,14 +122,14 @@ Item {
IpcHandler { IpcHandler {
target: "powerPanel" target: "powerPanel"
function toggle() { function toggle() {
powerPanel.toggle() powerPanel.toggle(getActiveScreen())
} }
} }
IpcHandler { IpcHandler {
target: "sidePanel" target: "sidePanel"
function toggle() { function toggle() {
sidePanel.toggle() sidePanel.toggle(getActiveScreen())
} }
} }

View file

@ -11,10 +11,16 @@ NPanel {
id: root id: root
// Panel configuration // Panel configuration
preferredWidth: 500 panelWidth: {
preferredWidthRatio: 0.3 var w = Math.round(Math.max(screen?.width * 0.3, 500) * scaling)
preferredHeight: 600 w = Math.min(w, screen?.width - Style.marginL * 2)
preferredHeightRatio: 0.5 return w
}
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)
@ -281,12 +287,9 @@ NPanel {
} }
// Results list // Results list
NListView { ListView {
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
@ -303,6 +306,10 @@ NPanel {
} }
} }
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
delegate: Rectangle { delegate: Rectangle {
id: entry id: entry

View file

@ -37,7 +37,8 @@ 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(app => createResultEntry(app)) return entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(
app => createResultEntry(app))
} }
// Use fuzzy search if available, fallback to simple search // Use fuzzy search if available, fallback to simple search
@ -56,7 +57,8 @@ 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(searchTerm) return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(
searchTerm)
}).sort((a, b) => { }).sort((a, b) => {
// Prioritize name matches // Prioritize name matches
const aName = a.name.toLowerCase() const aName = a.name.toLowerCase()
@ -83,10 +85,7 @@ 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}`)
if (app.runInTerminal) Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"])
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,7 +8,8 @@ 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(query.substring(1))) return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(
query.substring(1)))
} }
function commands() { function commands() {

View file

@ -12,7 +12,6 @@ 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: {
@ -29,6 +28,12 @@ Scope {
return return
} }
if (currentText === "") {
errorMessage = "Password required"
showFailure = true
return
}
root.unlockInProgress = true root.unlockInProgress = true
errorMessage = "" errorMessage = ""
showFailure = false showFailure = false
@ -43,12 +48,11 @@ Scope {
user: Quickshell.env("USER") user: Quickshell.env("USER")
onPamMessage: { onPamMessage: {
Logger.log("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired) Logger.log("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:",
responseRequired)
if (messageIsError) { if (messageIsError) {
errorMessage = message errorMessage = message
} else {
infoMessage = message
} }
if (responseRequired) { if (responseRequired) {

View file

@ -62,7 +62,8 @@ Loader {
Item { Item {
id: keyboardLayout id: keyboardLayout
property string currentLayout: (typeof KeyboardLayoutService !== 'undefined' && KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown" property string currentLayout: (typeof KeyboardLayoutService !== 'undefined'
&& KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
} }
Image { Image {
@ -226,10 +227,12 @@ 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 * 2 - 1 - index) property int mirroredValueIndex: index < CavaService.values.length ? index : (CavaService.values.length
* 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(2, CavaService.values[mirroredValueIndex] * 30 * scaling) property real mirroredBarLength: Math.max(
2, CavaService.values[mirroredValueIndex] * 30 * scaling)
property real mirroredBarWidth: 3 * scaling property real mirroredBarWidth: 3 * scaling
width: mirroredBarWidth width: mirroredBarWidth
height: mirroredBarLength height: mirroredBarLength
@ -425,7 +428,8 @@ Loader {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: batteryIndicator.batteryVisible visible: batteryIndicator.batteryVisible
NIcon { NIcon {
icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging, batteryIndicator.isReady) icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
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
@ -511,7 +515,6 @@ 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
@ -541,7 +544,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 && !lockContext.unlockInProgress visible: passwordInput.activeFocus
SequentialAnimation { SequentialAnimation {
id: typingEffect id: typingEffect
@ -585,7 +588,7 @@ Loader {
NText { NText {
text: { text: {
if (lockContext.unlockInProgress) if (lockContext.unlockInProgress)
return lockContext.infoMessage || "Authenticating..." return "Authenticating..."
if (lockContext.showFailure && lockContext.errorMessage) if (lockContext.showFailure && lockContext.errorMessage)
return lockContext.errorMessage return lockContext.errorMessage
if (lockContext.showFailure) if (lockContext.showFailure)
@ -747,7 +750,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." text: "Shut down the computer."
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -798,7 +801,7 @@ Loader {
id: restartTooltipText id: restartTooltipText
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginM * scaling
anchors.fill: parent anchors.fill: parent
text: "Restart." text: "Restart the computer."
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -850,7 +853,7 @@ Loader {
id: suspendTooltipText id: suspendTooltipText
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginM * scaling
anchors.fill: parent anchors.fill: parent
text: "Suspend." text: "Suspend the system."
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,7 +25,10 @@ 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 && (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(modelData.name) || (Settings.data.notifications.monitors.length === 0)) : false active: Settings.isLoaded && modelData
&& (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)
@ -33,50 +36,13 @@ Variants {
screen: modelData screen: modelData
color: Color.transparent color: Color.transparent
// Position based on bar location - always at top // Position based on bar location
anchors.top: true anchors.top: Settings.data.bar.position === "top"
anchors.right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom" anchors.bottom: Settings.data.bar.position === "bottom"
anchors.left: Settings.data.bar.position === "left" anchors.right: true
margins.top: Settings.data.bar.position === "top" ? (Style.barHeight + Style.marginM) * scaling : 0
margins.top: { margins.bottom: Settings.data.bar.position === "bottom" ? (Style.barHeight + Style.marginM) * scaling : 0
switch (Settings.data.bar.position) { margins.right: Style.marginM * scaling
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
@ -114,10 +80,10 @@ Variants {
// Main notification container // Main notification container
ColumnLayout { ColumnLayout {
id: notificationStack id: notificationStack
// Position based on bar location - always at top // Position based on bar location
anchors.top: parent.top anchors.top: Settings.data.bar.position === "top" ? parent.top : undefined
anchors.right: (Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom") ? parent.right : undefined anchors.bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined
anchors.left: Settings.data.bar.position === "left" ? parent.left : undefined anchors.right: parent.right
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
width: 360 * scaling width: 360 * scaling
visible: true visible: true
@ -215,7 +181,8 @@ Variants {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NText { NText {
text: `${(model.appName || model.desktopEntry) || "Unknown App"} · ${NotificationService.formatTimestamp(model.timestamp)}` text: `${(model.appName || model.desktopEntry)
|| "Unknown App"} · ${NotificationService.formatTimestamp(model.timestamp)}`
color: Color.mSecondary color: Color.mSecondary
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
} }
@ -282,7 +249,8 @@ Variants {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: model.rawNotification && model.rawNotification.actions && model.rawNotification.actions.length > 0 visible: model.rawNotification && model.rawNotification.actions
&& model.rawNotification.actions.length > 0
property var notificationActions: model.rawNotification ? model.rawNotification.actions : [] property var notificationActions: model.rawNotification ? model.rawNotification.actions : []
@ -325,7 +293,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,10 +12,9 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
preferredWidth: 380 panelWidth: 380 * scaling
preferredHeight: 500 panelHeight: 500 * scaling
panelAnchorRight: Settings.data.bar.position === "right" panelAnchorRight: true
panelKeyboardFocus: true
panelContent: Rectangle { panelContent: Rectangle {
id: notificationRect id: notificationRect
@ -57,15 +56,12 @@ NPanel {
icon: "trash" icon: "trash"
tooltipText: "Clear history" tooltipText: "Clear history"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: NotificationService.clearHistory()
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()
@ -119,13 +115,10 @@ NPanel {
} }
// Notification list // Notification list
NListView { ListView {
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
@ -136,7 +129,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.mTertiary : Color.mSurfaceVariant color: notificationMouseArea.containsMouse ? Color.mSecondary : 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)
@ -152,7 +145,13 @@ 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 && 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")))) 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"))))
borderColor: Color.transparent borderColor: Color.transparent
borderWidth: 0 borderWidth: 0
visible: true visible: true
@ -169,7 +168,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.mOnTertiary : Color.mPrimary color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mPrimary
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.fillWidth: true Layout.fillWidth: true
maximumLineCount: 2 maximumLineCount: 2
@ -179,7 +178,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.mOnTertiary : Color.mOnSurface color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.fillWidth: true Layout.fillWidth: true
maximumLineCount: 3 maximumLineCount: 3
@ -190,7 +189,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.mOnTertiary : Color.mOnSurface color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
Layout.fillWidth: true Layout.fillWidth: true
} }
} }

View file

@ -13,8 +13,8 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
preferredWidth: 440 panelWidth: 440 * scaling
preferredHeight: 410 panelHeight: 380 * scaling
panelAnchorHorizontalCenter: true panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true panelAnchorVerticalCenter: true
panelKeyboardFocus: true panelKeyboardFocus: true
@ -224,7 +224,6 @@ NPanel {
root.close() root.close()
} }
} }
context: Qt.WidgetShortcut
enabled: root.opened enabled: root.opened
} }
@ -263,7 +262,8 @@ 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(timeRemaining / 1000)} seconds...` : "Power Menu" text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil(
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,10 +292,6 @@ NPanel {
} }
} }
NDivider {
Layout.fillWidth: true
}
// Power options // Power options
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
@ -341,7 +337,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.mTertiary return Color.mSecondary
} }
return Color.transparent return Color.transparent
} }
@ -371,7 +367,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.mOnTertiary return Color.mOnSecondary
return Color.mOnSurface return Color.mOnSurface
} }
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
@ -405,7 +401,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.mOnTertiary return Color.mOnSecondary
return Color.mOnSurface return Color.mOnSurface
} }
@ -430,7 +426,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.mOnTertiary return Color.mOnSecondary
return Color.mOnSurfaceVariant return Color.mOnSurfaceVariant
} }
opacity: Style.opacityHeavy opacity: Style.opacityHeavy

View file

@ -19,8 +19,10 @@ 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: {
@ -41,19 +43,17 @@ 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 % 6) { switch (totalSum % 5) {
case 0: case 0:
return [Color.mPrimary, Color.mOnPrimary] return Color.mPrimary
case 1: case 1:
return [Color.mSecondary, Color.mOnSecondary] return Color.mSecondary
case 2: case 2:
return [Color.mTertiary, Color.mOnTertiary] return Color.mTertiary
case 3: case 3:
return [Color.mError, Color.mOnError] return Color.mError
case 4: case 4:
return [Color.mOnSurface, Color.mSurface] return Color.mOnSurface
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)[0] color: root.getWidgetColor(modelData)
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: Style.animationFast duration: 150
} }
} }
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: Style.animationFast duration: 150
} }
} }
@ -164,7 +164,7 @@ NBox {
NText { NText {
text: modelData.id text: modelData.id
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
color: root.getWidgetColor(modelData)[1] color: Color.mOnPrimary
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: Color.transparent 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: false propagateComposedEvents: !dragStarted
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,10 +339,12 @@ 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, 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2)) const leftDist = Math.sqrt(Math.pow(mouseX - widget.x,
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), 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2)) const rightDist = Math.sqrt(Math.pow(mouseX - (widget.x + widget.width),
2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
if (leftDist < minDistance) { if (leftDist < minDistance) {
minDistance = leftDist minDistance = leftDist
@ -353,7 +355,8 @@ 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, widget.y) bestPosition = Qt.point(widget.x + widget.width + Style.marginXS * scaling - dropIndicator.width / 2,
widget.y)
} }
} }
@ -365,7 +368,8 @@ 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), firstWidget.y) bestPosition = Qt.point(Math.max(0, firstWidget.x - dropIndicator.width - Style.marginS * scaling),
firstWidget.y)
} }
} }
} }
@ -415,7 +419,8 @@ 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 && mouse.y <= widget.y + widget.height) { if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && mouse.y >= widget.y
&& 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)
@ -453,7 +458,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)[0] dragGhost.color = root.getWidgetColor(draggedModelData)
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: 500 * scaling width: 420 * scaling
height: content.implicitHeight + padding * 2 height: content.implicitHeight + padding * 2
padding: Style.marginXL * scaling padding: Style.marginXL * scaling
modal: true modal: true
@ -46,7 +46,6 @@ 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,36 +14,22 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold !== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
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.displayMode = valueDisplayMode settings.alwaysShowPercentage = valueAlwaysShowPercentage
settings.warningThreshold = valueWarningThreshold settings.warningThreshold = valueWarningThreshold
return settings return settings
} }
NComboBox { NToggle {
label: "Display mode" label: "Always show percentage"
description: "Choose how you'd like this value to appear." checked: root.valueAlwaysShowPercentage
minimumWidth: 134 * scaling onToggled: checked => root.valueAlwaysShowPercentage = checked
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,33 +14,18 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.displayMode = valueDisplayMode settings.alwaysShowPercentage = valueAlwaysShowPercentage
return settings return settings
} }
NComboBox { NToggle {
label: "Display mode" label: "Always show percentage"
description: "Choose how you'd like this value to appear." checked: valueAlwaysShowPercentage
minimumWidth: 134 * scaling onToggled: checked => valueAlwaysShowPercentage = checked
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,41 +14,24 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property string valueDisplayFormat: widgetData.displayFormat !== undefined ? widgetData.displayFormat : widgetMetadata.displayFormat property bool valueShowDate: widgetData.showDate !== undefined ? widgetData.showDate : widgetMetadata.showDate
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.displayFormat = valueDisplayFormat settings.showDate = valueShowDate
settings.use12HourClock = valueUse12h settings.use12HourClock = valueUse12h
settings.showSeconds = valueShowSeconds
settings.reverseDayMonth = valueReverseDayMonth settings.reverseDayMonth = valueReverseDayMonth
return settings return settings
} }
NComboBox { NToggle {
label: "Display Format" label: "Show date"
model: ListModel { checked: valueShowDate
ListElement { onToggled: checked => valueShowDate = checked
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 {
@ -57,6 +40,12 @@ 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,8 +19,6 @@ 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
} }
@ -50,16 +48,18 @@ ColumnLayout {
Popup { Popup {
id: iconPicker id: iconPicker
modal: true modal: true
width: { property real panelWidth: {
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
} }
height: { property real panelHeight: {
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,12 +117,10 @@ ColumnLayout {
} }
// Icon grid // Icon grid
NScrollView { ScrollView {
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
@ -230,33 +228,4 @@ 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

@ -1,46 +0,0 @@
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,33 +14,18 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.displayMode = valueDisplayMode settings.alwaysShowPercentage = valueAlwaysShowPercentage
return settings return settings
} }
NComboBox { NToggle {
label: "Display mode" label: "Always show percentage"
description: "Choose how you'd like this value to appear." checked: valueAlwaysShowPercentage
minimumWidth: 134 * scaling onToggled: checked => valueAlwaysShowPercentage = checked
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,15 +16,20 @@ 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 !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent
property bool valueShowNetworkStats: widgetData.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
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
@ -48,6 +53,14 @@ 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,33 +14,18 @@ ColumnLayout {
property var widgetMetadata: null property var widgetMetadata: null
// Local state // Local state
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.displayMode = valueDisplayMode settings.alwaysShowPercentage = valueAlwaysShowPercentage
return settings return settings
} }
NComboBox { NToggle {
label: "Display mode" label: "Always show percentage"
description: "Choose how you'd like this value to appear." checked: valueAlwaysShowPercentage
minimumWidth: 134 * scaling onToggled: checked => valueAlwaysShowPercentage = checked
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,7 +16,6 @@ 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
} }
@ -42,12 +41,4 @@ 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,11 +11,16 @@ import qs.Widgets
NPanel { NPanel {
id: root id: root
preferredWidth: 1000 panelWidth: {
preferredHeight: 1000 var w = Math.round(Math.max(screen?.width * 0.4, 1000) * scaling)
preferredWidthRatio: 0.4 w = Math.min(w, screen?.width - Style.marginL * 2)
preferredHeightRatio: 0.75 return w
}
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
@ -26,14 +31,13 @@ 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,
@ -68,10 +72,15 @@ 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 {}
@ -108,14 +117,6 @@ 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() {
@ -129,11 +130,6 @@ 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",
@ -149,16 +145,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",
@ -227,7 +223,8 @@ 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, 1.0 - scrollBar.size) scrollBar.position = Math.min(scrollBar.position + stepSize / activeScrollView.contentHeight,
1.0 - scrollBar.size)
} }
} }
@ -243,7 +240,8 @@ 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, 1.0 - scrollBar.size) scrollBar.position = Math.min(scrollBar.position + pageSize / activeScrollView.contentHeight,
1.0 - scrollBar.size)
} }
} }
@ -468,7 +466,7 @@ NPanel {
NIcon { NIcon {
icon: root.tabsModel[currentTabIndex]?.icon icon: root.tabsModel[currentTabIndex]?.icon
color: Color.mPrimary color: Color.mPrimary
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXL * scaling
} }
// Main title // Main title
@ -484,7 +482,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()
} }
@ -524,11 +522,11 @@ NPanel {
anchors.fill: parent anchors.fill: parent
pressDelay: 200 pressDelay: 200
NScrollView { ScrollView {
id: scrollView id: scrollView
anchors.fill: parent anchors.fill: parent
horizontalPolicy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded ScrollBar.vertical.policy: ScrollBar.AsNeeded
padding: Style.marginL * scaling padding: Style.marginL * scaling
clip: true clip: true

View file

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

View file

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

View file

@ -1,7 +1,6 @@
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
@ -9,20 +8,6 @@ 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() {
@ -40,153 +25,65 @@ ColumnLayout {
} }
} }
NHeader {
label: "Appearance"
description: "Configure bar appearance and positioning."
}
RowLayout {
NComboBox {
Layout.fillWidth: true
label: "Bar Position"
description: "Choose where to place the bar on the screen."
model: ListModel {
ListElement {
key: "top"
name: "Top"
}
ListElement {
key: "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
}
}
ColumnLayout { ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true
NLabel {
label: "Background Opacity"
description: "Adjust the background opacity of the bar."
}
NValueSlider {
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
}
// Floating bar options - only show when floating is enabled
ColumnLayout {
visible: Settings.data.bar.floating
spacing: Style.marginS * scaling
Layout.fillWidth: true
NLabel {
label: "Margins"
description: "Adjust the margins around the floating bar."
}
RowLayout { RowLayout {
Layout.fillWidth: true NComboBox {
spacing: Style.marginL * scaling
ColumnLayout {
spacing: Style.marginXXS * scaling
NText {
text: "Vertical"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.marginVertical
onMoved: value => Settings.data.bar.marginVertical = value
text: Math.round(Settings.data.bar.marginVertical * 100) + "%"
}
}
ColumnLayout {
spacing: Style.marginXXS * scaling
NText {
text: "Horizontal"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
}
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 Layout.fillWidth: true
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}` label: "Bar Position"
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})` description: "Choose where to place the bar on the screen."
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1 model: ListModel {
onToggled: checked => { ListElement {
if (checked) { key: "top"
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name) name: "Top"
} else { }
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name) ListElement {
} key: "bottom"
} name: "Bottom"
}
}
currentKey: Settings.data.bar.position
onSelected: key => Settings.data.bar.position = key
}
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Background Opacity"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Adjust the background opacity of the bar."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.backgroundOpacity
onMoved: Settings.data.bar.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
} }
} }
} }
@ -202,9 +99,20 @@ ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Widgets Positioning" text: "Widgets Positioning"
description: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets." font.pointSize: Style.fontSizeXXL * scaling
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
@ -293,7 +201,8 @@ ColumnLayout {
} }
function _reorderWidgetInSection(section, fromIndex, toIndex) { function _reorderWidgetInSection(section, fromIndex, toIndex) {
if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) { if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0
&& 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

@ -0,0 +1,341 @@
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: Style.marginL * scaling spacing: 0
// 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
NHeader { ColumnLayout {
label: "Behavior" spacing: Style.marginL * scaling
description: "Main settings for Noctalia's colors." Layout.fillWidth: true
}
// 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,9 +151,19 @@ ColumnLayout {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Predefined Color Schemes" text: "Predefined Color Schemes"
description: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper." font.pointSize: Style.fontSizeXXL * scaling
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
@ -176,7 +186,9 @@ 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 && (Settings.data.colorSchemes.predefinedScheme === modelData.split("/").pop().replace(".json", ""))) ? Color.mSecondary : Color.mOutline border.color: (!Settings.data.colorSchemes.useWallpaperColors
&& (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
@ -269,21 +281,23 @@ ColumnLayout {
// Selection indicator (Checkmark) // Selection indicator (Checkmark)
Rectangle { Rectangle {
visible: !Settings.data.colorSchemes.useWallpaperColors && (Settings.data.colorSchemes.predefinedScheme === schemePath.split("/").pop().replace(".json", "")) visible: !Settings.data.colorSchemes.useWallpaperColors
&& (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: 28 * scaling width: 24 * scaling
height: 28 * scaling height: 24 * scaling
radius: width * 0.5 radius: width * 0.5
color: Color.mSecondary color: Color.mPrimary
NIcon { NText {
icon: "check"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSecondary
anchors.centerIn: parent anchors.centerIn: parent
text: "✓"
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
} }
} }
@ -318,187 +332,146 @@ ColumnLayout {
visible: Settings.data.colorSchemes.useWallpaperColors visible: Settings.data.colorSchemes.useWallpaperColors
} }
// Matugen template toggles organized by category // Matugen template toggles (moved from MatugenTab)
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
// UI Components ColumnLayout {
NCollapsible { spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
label: "UI"
description: "Desktop environment and UI toolkit theming."
defaultExpanded: false
NCheckbox { NText {
label: "GTK 4 (libadwaita)" text: "Matugen Templates"
description: "Write ~/.config/gtk-4.0/gtk.css" font.pointSize: Style.fontSizeXXL * scaling
checked: Settings.data.matugen.gtk4 font.weight: Style.fontWeightBold
onToggled: checked => { color: Color.mSecondary
Settings.data.matugen.gtk4 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
} }
NCheckbox { NText {
label: "GTK 3" text: "Select which external components Matugen should apply theming to."
description: "Write ~/.config/gtk-3.0/gtk.css" font.pointSize: Style.fontSizeM * scaling
checked: Settings.data.matugen.gtk3 color: Color.mOnSurfaceVariant
onToggled: checked => { Layout.fillWidth: true
Settings.data.matugen.gtk3 = checked wrapMode: Text.WordWrap
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()
}
} }
} }
// Terminal Emulators NCheckbox {
NCollapsible { label: "GTK 4 (libadwaita)"
Layout.fillWidth: true description: "Write ~/.config/gtk-4.0/gtk.css"
label: "Terminal" checked: Settings.data.matugen.gtk4
description: "Terminal emulator theming." onToggled: checked => {
defaultExpanded: false Settings.data.matugen.gtk4 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
NCheckbox { MatugenService.generateFromWallpaper()
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()
}
}
}
} }
// Applications NCheckbox {
NCollapsible { label: "GTK 3"
Layout.fillWidth: true description: "Write ~/.config/gtk-3.0/gtk.css"
label: "Programs" checked: Settings.data.matugen.gtk3
description: "Application-specific theming." onToggled: checked => {
defaultExpanded: false Settings.data.matugen.gtk3 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
NCheckbox { MatugenService.generateFromWallpaper()
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 NCheckbox {
NCollapsible { label: "Qt6ct"
Layout.fillWidth: true description: "Write ~/.config/qt6ct/colors/noctalia.conf"
label: "Misc" checked: Settings.data.matugen.qt6
description: "Additional configuration options." onToggled: checked => {
defaultExpanded: false Settings.data.matugen.qt6 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox { NCheckbox {
label: "User Templates" label: "Qt5ct"
description: "Enable user-defined Matugen config from ~/.config/matugen/config.toml" description: "Write ~/.config/qt5ct/colors/noctalia.conf"
checked: Settings.data.matugen.enableUserTemplates checked: Settings.data.matugen.qt5
onToggled: checked => { onToggled: checked => {
Settings.data.matugen.enableUserTemplates = checked Settings.data.matugen.qt5 = checked
if (Settings.data.colorSchemes.useWallpaperColors) if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper() 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.topMargin: Style.marginM * scaling
Layout.bottomMargin: Style.marginM * scaling
}
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,7 +2,6 @@ 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
@ -10,68 +9,49 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
// Time dropdown options (00:00 .. 23:30) // Helper functions to update arrays immutably
ListModel { function addMonitor(list, name) {
id: timeOptions const arr = (list || []).slice()
if (!arr.includes(name))
arr.push(name)
return arr
} }
Component.onCompleted: { function removeMonitor(list, name) {
for (var h = 0; h < 24; h++) { return (list || []).filter(function (n) {
for (var m = 0; m < 60; m += 30) { return n !== name
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 NText {
Process { text: "Monitor-specific configuration"
id: wlsunsetCheck font.pointSize: Style.fontSizeL * scaling
command: ["which", "wlsunset"] font.weight: Style.fontWeightBold
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 NText {
text: "Bars and notifications appear on all displays by default. Choose specific displays below to limit where they're shown."
NHeader { font.pointSize: Style.fontSizeM * scaling
label: "Monitor-specific configuration" color: Color.mOnSurfaceVariant
description: "Configure scaling and brightness settings individually for each connected display." wrapMode: Text.WordWrap
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
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling Layout.minimumWidth: 550 * scaling
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: Color.mSurfaceVariant color: Color.mSurface
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) {
@ -88,100 +68,122 @@ ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
NText { NText {
text: (`${modelData.name}: ${modelData.model}` || "Unknown") text: (modelData.name || "Unknown")
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mPrimary color: Color.mSecondary
} }
NText { NText {
text: `Resolution: ${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})` text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${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 {
spacing: Style.marginS * scaling
Layout.fillWidth: true
RowLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NText {
text: "Scale"
Layout.preferredWidth: 80 * scaling
}
NValueSlider {
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
}
// Reset button container
Item {
Layout.preferredWidth: 40 * scaling
Layout.preferredHeight: 30 * scaling
NIconButton {
icon: "refresh"
sizeRatio: 0.8
tooltipText: "Reset scaling"
onClicked: ScalingService.setScreenScale(modelData, 1.0)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
// Brightness
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
RowLayout { NToggle {
Layout.fillWidth: true 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)
}
}
}
NText { NToggle {
text: "Brightness" Layout.fillWidth: true
Layout.preferredWidth: 80 * scaling 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)
}
}
}
NValueSlider { 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
RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
from: 0 spacing: Style.marginL * scaling
to: 1
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5 ColumnLayout {
stepSize: 0.01 spacing: Style.marginXXS * scaling
onPressedChanged: (pressed, value) => brightnessMonitor.setBrightness(value) Layout.fillWidth: true
text: brightnessMonitor ? Math.round(brightnessMonitor.brightness * 100) + "%" : "N/A"
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
}
} }
// Empty container to match scale row layout RowLayout {
Item { spacing: Style.marginS * scaling
Layout.preferredWidth: 40 * scaling Layout.fillWidth: true
Layout.preferredHeight: 30 * scaling
// Method text positioned in the button area NSlider {
NText { id: scaleSlider
text: brightnessMonitor ? brightnessMonitor.method : "" from: 0.7
font.pointSize: Style.fontSizeXS * scaling to: 1.8
color: Color.mOnSurfaceVariant stepSize: 0.01
anchors.right: parent.right value: localScaling
anchors.verticalCenter: parent.verticalCenter onPressedChanged: ScalingService.setScreenScale(modelData, value)
horizontalAlignment: Text.AlignRight Layout.fillWidth: true
Layout.minimumWidth: 150 * scaling
}
NIconButton {
icon: "refresh"
tooltipText: "Reset scaling"
onClicked: ScalingService.setScreenScale(modelData, 1.0)
} }
} }
} }
@ -190,216 +192,4 @@ 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

@ -1,122 +0,0 @@
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,11 +9,6 @@ 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
@ -53,9 +48,19 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "User Interface" text: "User Interface"
description: "Main settings for the user interface." font.pointSize: Style.fontSizeXXL * scaling
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 {
@ -74,14 +79,23 @@ ColumnLayout {
description: "Adjust the rounded border of all UI elements." description: "Adjust the rounded border of all UI elements."
} }
NValueSlider { RowLayout {
Layout.fillWidth: true NSlider {
from: 0 Layout.fillWidth: true
to: 1 from: 0
stepSize: 0.01 to: 1
value: Settings.data.general.radiusRatio stepSize: 0.01
onMoved: value => Settings.data.general.radiusRatio = value value: Settings.data.general.radiusRatio
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%" onMoved: Settings.data.general.radiusRatio = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
} }
} }
@ -95,18 +109,26 @@ ColumnLayout {
description: "Adjust global animation speed." description: "Adjust global animation speed."
} }
NValueSlider { RowLayout {
Layout.fillWidth: true NSlider {
from: 0.1 Layout.fillWidth: true
to: 2.0 from: 0.1
stepSize: 0.01 to: 2.0
value: Settings.data.general.animationSpeed stepSize: 0.01
onMoved: value => Settings.data.general.animationSpeed = value value: Settings.data.general.animationSpeed
text: Math.round(Settings.data.general.animationSpeed * 100) + "%" onMoved: Settings.data.general.animationSpeed = value
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
@ -117,42 +139,57 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Screen Corners" text: "Dock"
description: "Customize screen corner rounding and visual effects." font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
NToggle { NToggle {
label: "Show Screen Corners" label: "Auto-hide Dock"
description: "Display rounded corners on the edge of the screen." description: "Automatically hide the dock when not in use."
checked: Settings.data.general.showScreenCorners checked: Settings.data.dock.autoHide
onToggled: checked => Settings.data.general.showScreenCorners = checked onToggled: checked => Settings.data.dock.autoHide = 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
NLabel { NText {
label: "Screen Corners Radius" text: "Dock Background Opacity"
description: "Adjust the rounded corners of the screen." font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
} }
NValueSlider { NText {
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
stepSize: 0.01 RowLayout {
value: Settings.data.general.screenRadiusRatio NSlider {
onMoved: value => Settings.data.general.screenRadiusRatio = value Layout.fillWidth: true
text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%" from: 0
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
}
} }
} }
} }
@ -166,10 +203,12 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
NHeader { text: "Fonts"
label: "Fonts" font.pointSize: Style.fontSizeXXL * scaling
description: "Configure interface typography." font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// Font configuration section // Font configuration section
@ -177,13 +216,12 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NSearchableComboBox { NComboBox {
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) {
@ -191,13 +229,12 @@ ColumnLayout {
} }
} }
NSearchableComboBox { NComboBox {
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) {
@ -205,13 +242,12 @@ ColumnLayout {
} }
} }
NSearchableComboBox { NComboBox {
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,11 +10,6 @@ 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,97 +7,104 @@ 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.marginXXS * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true
NText { NComboBox {
text: "Background Opacity" id: launcherPosition
font.pointSize: Style.fontSizeL * scaling label: "Position"
font.weight: Style.fontWeightBold description: "Choose where the Launcher panel appears."
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
}
} }
NValueSlider { ColumnLayout {
id: launcherBgOpacity spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
from: 0.0
to: 1.0 NText {
stepSize: 0.01 text: "Background Opacity"
value: Settings.data.appLauncher.backgroundOpacity font.pointSize: Style.fontSizeL * scaling
onMoved: value => Settings.data.appLauncher.backgroundOpacity = value font.weight: Style.fontWeightBold
text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%" 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
}
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,11 +11,6 @@ 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

@ -1,162 +0,0 @@
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,15 +10,11 @@ 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"
@ -57,8 +53,12 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Video Settings" text: "Video Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
} }
// Source // Source
@ -203,8 +203,12 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Audio Settings" text: "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,6 +9,7 @@ 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: []
@ -41,9 +42,11 @@ ColumnLayout {
} }
// Current wallpaper display // Current wallpaper display
NHeader { NText {
label: "Current Wallpaper" text: "Current Wallpaper"
description: "Preview and manage your desktop background." font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
Rectangle { Rectangle {
@ -77,9 +80,18 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
// Wallpaper grid // Wallpaper grid
NHeader { NText {
label: "Wallpaper Selector" text: "Wallpaper Selector"
description: "Click on a wallpaper to set it as your current wallpaper." font.pointSize: Style.fontSizeXXL * scaling
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,12 +9,6 @@ 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"
@ -28,7 +22,6 @@ 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."
@ -68,7 +61,7 @@ ColumnLayout {
delegate: RowLayout { delegate: RowLayout {
NText { NText {
text: (modelData.name || "Unknown") text: (modelData.name || "Unknown")
color: Color.mPrimary color: Color.mSecondary
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
Layout.preferredWidth: 90 * scaling Layout.preferredWidth: 90 * scaling
} }
@ -96,8 +89,11 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Look & Feel" text: "Look & Feel"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
// Fill Mode // Fill Mode
@ -138,14 +134,21 @@ ColumnLayout {
description: "Duration of transition animations in seconds." description: "Duration of transition animations in seconds."
} }
NValueSlider { RowLayout {
Layout.fillWidth: true spacing: Style.marginL * scaling
from: 500 NSlider {
to: 10000 Layout.fillWidth: true
stepSize: 100 from: 500
value: Settings.data.wallpaper.transitionDuration to: 10000
onMoved: value => Settings.data.wallpaper.transitionDuration = value stepSize: 100
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s" value: Settings.data.wallpaper.transitionDuration
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
}
} }
} }
@ -156,13 +159,20 @@ ColumnLayout {
description: "Duration of transition animations in seconds." description: "Duration of transition animations in seconds."
} }
NValueSlider { RowLayout {
Layout.fillWidth: true spacing: Style.marginL * scaling
from: 0.0 NSlider {
to: 1.0 Layout.fillWidth: true
value: Settings.data.wallpaper.transitionEdgeSmoothness from: 0.0
onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value to: 1.0
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%" value: Settings.data.wallpaper.transitionEdgeSmoothness
onMoved: Settings.data.wallpaper.transitionEdgeSmoothness = value
cutoutColor: Color.mSurface
}
NText {
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
}
} }
} }
} }
@ -179,8 +189,11 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Automation" text: "Automation"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
// Random Wallpaper // Random Wallpaper

View file

@ -7,12 +7,6 @@ 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 {
@ -63,9 +57,11 @@ ColumnLayout {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
Layout.fillWidth: true Layout.fillWidth: true
NHeader { NText {
label: "Weather" text: "Weather"
description: "Configure weather display preferences and temperature units." font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
} }
NToggle { NToggle {
@ -75,10 +71,4 @@ 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,8 +10,12 @@ 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
@ -232,7 +236,9 @@ 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 && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio property real effectiveRatio: (MediaService.isSeeking
&& localSeekRatio >= 0) ? Math.max(0, Math.min(1,
localSeekRatio)) : progressRatio
// Debounced backend seek during drag // Debounced backend seek during drag
Timer { Timer {
@ -242,7 +248,8 @@ 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(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) { if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(
next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
MediaService.seekByRatio(next) MediaService.seekByRatio(next)
progressWrapper.lastSentSeekRatio = next progressWrapper.lastSentSeekRatio = next
} }
@ -258,6 +265,7 @@ 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,11 +9,13 @@ import qs.Widgets
// Power Profiles: performance, balanced, eco // Power Profiles: performance, balanced, eco
NBox { NBox {
Layout.fillWidth: true
property real spacing: 0 Layout.preferredWidth: 1
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
@ -29,7 +31,8 @@ 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 && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant colorBg: (enabled
&& 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) {
@ -43,7 +46,8 @@ 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 && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant colorBg: (enabled
&& 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) {
@ -57,7 +61,8 @@ 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 && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant colorBg: (enabled
&& 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,6 +16,10 @@ 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
@ -59,7 +63,7 @@ NBox {
tooltipText: "Open settings." tooltipText: "Open settings."
onClicked: { onClicked: {
settingsPanel.requestedTab = SettingsPanel.Tab.General settingsPanel.requestedTab = SettingsPanel.Tab.General
settingsPanel.open() settingsPanel.open(screen)
} }
} }
@ -68,7 +72,7 @@ NBox {
icon: "power" icon: "power"
tooltipText: "Power menu." tooltipText: "Power menu."
onClicked: { onClicked: {
powerPanel.open() powerPanel.open(screen)
sidePanel.close() sidePanel.close()
} }
} }

View file

@ -8,6 +8,9 @@ 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