import { Palette } from "std-widgets.slint"; // One geometry contract for every workspace. Feature views may choose their // content, but they must not invent their own rail, sidebar, toolbar, or touch // target dimensions. export global LayoutMetrics { out property product-rail-width: 56px; out property sidebar-width: 230px; out property list-pane-width: 390px; out property divider-width: 1px; // Resizable list panes retain the established 390px default/minimum and // leave enough room for the adjacent message, contact, or file detail. out property detail-pane-min-width: 300px; out property pane-resize-hit-width: 9px; out property pane-resize-keyboard-step: 16px; out property compact-navigation-height: 80px; out property compact-header-height: 60px; out property touch-target: 44px; out property desktop-icon-button-size: 40px; out property icon-size: 18px; out property desktop-toolbar-icon-size: icon-size; out property compact-toolbar-icon-size: icon-size; // Feature sidebars keep a full 12px content gutter. Scrollable trees add // their own scrollbar breathing room inside this boundary. out property sidebar-inset: 12px; out property sidebar-scrollbar-gutter: 12px; // The rail starts at 8px and its 48px destination row is centered at // 32px. A 34px feature action therefore begins at 15px. out property sidebar-action-top: 15px; out property sidebar-action-height: 34px; out property workspace-action-center-y: 32px; out property desktop-mail-toolbar-height: 64px; out property content-inset: 12px; out property list-row-inset: 12px; // Rows stop short of the overlaid scrollbar so selection and focus // outlines never paint over it. out property list-scrollbar-gutter: 12px; out property compact-inset: 10px; out property control-height: 40px; out property corner-radius: 8px; } // Runtime motion stays enabled by default. Deterministic renderers such as the // README screenshot generator can opt out so they capture the settled state // without changing the motion users see in the application. export global MotionSettings { in-out property enabled: true; } // Mail actions and their matching sidebar destinations share one semantic // icon palette. These colors stay stable across themes so the same action is // recognizable wherever it appears. export global MailIconPalette { out property inbox: #2f9cf4; out property starred: #f5b301; out property sent: #00a8f3; out property spam: #f59e0b; out property trash: #ff5b4d; out property archive: #8b5cf6; out property drafts: #d946ef; out property folders: #22a06b; out property other: #64748b; // The key on a message's verification code. out property verification-code: #f5b301; } // A file's type keeps one color wherever the file shows, in both themes. export global FileIconPalette { out property pdf: #e5484d; out property document: #2f81f7; out property spreadsheet: #22a06b; out property presentation: #ea580c; out property image: #8b5cf6; out property video: #db2777; out property audio: #0ea5e9; out property archive: #d97706; out property code: #64748b; out property calendar: #2f9cf4; out property other: #80817e; } // Settings destinations use the same preference-controlled color strategy as // mailbox icons: semantic colors by default, neutral colors in monochrome mode. export global SettingsIconPalette { out property general: #64748b; out property accounts: #3b82f6; out property notifications: #f59e0b; out property shortcuts: #22a06b; out property storage: #a855f7; out property services: AppTheme.dark ? #a5b4fc : #6366f1; out property about: #ec4899; } // Hosted services keep distinct identities in both light and dark palettes. export global ServiceIconPalette { out property sync: AppTheme.dark ? #60a5fa : #2563eb; out property business: AppTheme.dark ? #2dd4bf : #0f766e; } // Application colors live here instead of being re-declared by every custom // button and view. Palette.color-scheme is set by AppWindow's theme setting, // so all consumers update together when the user changes appearance. export global AppTheme { // Color scheme and palette are separate preferences. This lets every // preset follow the OS while a custom palette keeps independent light and // dark values. The default branch below is the original Flectar palette. in-out property preset: "default"; in-out property custom-light-primary: #0969da; in-out property custom-light-page-bg: #f2f2f0; in-out property custom-light-surface: #ffffff; in-out property custom-light-text: #202120; in-out property custom-light-border: #d9d9d6; in-out property custom-dark-primary: #0969da; in-out property custom-dark-page-bg: #111213; in-out property custom-dark-surface: #18191a; in-out property custom-dark-text: #f3f3f2; in-out property custom-dark-border: #3a3b3c; // Set with WindowChrome.top-bar, while the tab strip spans the window. in property top-bar: false; out property dark: Palette.color-scheme == ColorScheme.dark; // Each non-default preset starts from five user-facing foundation colors. // The rest of the semantic tokens are derived consistently, so adding a // component never expands the persisted theme schema. out property light-primary: preset == "default" ? #0969da : preset == "teal" ? #006b7a : preset == "green" ? #2f6f44 : preset == "purple" ? #78418a : custom-light-primary; out property dark-primary: preset == "default" ? #0969da : preset == "teal" ? #006b7a : preset == "green" ? #2f6f44 : preset == "purple" ? #78418a : custom-dark-primary; private property themed-primary: dark ? dark-primary : light-primary; private property themed-page-bg: preset == "teal" ? (dark ? #0b1519 : #edf5f7) : preset == "green" ? (dark ? #101711 : #f1f5ef) : preset == "purple" ? (dark ? #171119 : #f6f1f7) : dark ? custom-dark-page-bg : custom-light-page-bg; // Both schemes are exposed for message appearance independent of the UI. out property light-surface: preset == "default" ? #ffffff : preset == "teal" ? #fbfefe : preset == "green" ? #fcfdfb : preset == "purple" ? #fdfbfe : custom-light-surface; out property dark-surface: preset == "default" ? #18191a : preset == "teal" ? #122026 : preset == "green" ? #182019 : preset == "purple" ? #201722 : custom-dark-surface; out property light-text: preset == "default" ? #202120 : preset == "teal" ? #173039 : preset == "green" ? #213026 : preset == "purple" ? #332537 : custom-light-text; out property dark-text: preset == "default" ? #f3f3f2 : preset == "teal" ? #e5f0f2 : preset == "green" ? #e9f0e6 : preset == "purple" ? #f1e8f3 : custom-dark-text; private property themed-surface: dark ? dark-surface : light-surface; private property themed-text: dark ? dark-text : light-text; private property themed-border: preset == "teal" ? (dark ? #294047 : #c8dadd) : preset == "green" ? (dark ? #344039 : #ccd8c8) : preset == "purple" ? (dark ? #46354a : #dfd0e2) : dark ? custom-dark-border : custom-light-border; private property themed-accent: dark ? themed-primary.mix(white, 0.62) : themed-primary; out property page-bg: preset == "default" ? (dark ? #111213 : #f2f2f0) : themed-page-bg; out property surface: preset == "default" ? (dark ? #18191a : #ffffff) : themed-surface; out property surface-raised: preset == "default" ? (dark ? #131415 : #fafaf8) : themed-surface.mix(themed-page-bg, 0.72); // The sidebars' own tint, also a soft fill on the content. out property sidebar-tint: preset == "default" ? (dark ? #151617 : #f0f0ee) : themed-page-bg.mix(themed-surface, 0.72); // Under the tab strip, sidebars share the content's surface, so the // active tab opens into one color wherever it sits; dividers alone // separate the panes there. out property sidebar-bg: root.top-bar ? root.surface : root.sidebar-tint; out property control-bg: preset == "default" ? (dark ? #232425 : #f1f1ef) : themed-surface.mix(themed-text, dark ? 0.90 : 0.94); out property muted-control-bg: preset == "default" ? (dark ? #242526 : #e8e8e5) : themed-surface.mix(themed-text, dark ? 0.86 : 0.90); // Product actions deliberately keep the established Flectar blue in both // themes. The lighter dark-theme accent remains available for selection, // focus, and small icons where extra contrast is useful. out property primary: preset == "default" ? #0969da : themed-primary; out property primary-hover: preset == "default" ? #1675df : themed-primary.mix(white, 0.90); out property primary-pressed: preset == "default" ? #0757b5 : themed-primary.mix(black, 0.82); out property accent: preset == "default" ? (dark ? #5ca3ff : #0969da) : themed-accent; out property accent-hover: preset == "default" ? (dark ? #74b1ff : #1675df) : themed-accent.mix(white, 0.88); out property accent-pressed: preset == "default" ? (dark ? #428de8 : #0757b5) : themed-accent.mix(black, 0.84); out property accent-soft: preset == "default" ? (dark ? #26364a : #dcecff) : themed-accent.mix(themed-surface, dark ? 0.20 : 0.14); out property accent-pale: preset == "default" ? (dark ? #1c2a3a : #edf6ff) : themed-accent.mix(themed-surface, dark ? 0.11 : 0.07); out property selected-row: preset == "default" ? (dark ? #252627 : #e9e9e6) : themed-surface.mix(themed-text, dark ? 0.90 : 0.93); out property avatar-bg: preset == "default" ? (dark ? #303132 : #e6e6e2) : themed-surface.mix(themed-text, dark ? 0.84 : 0.89); out property avatar-selected-bg: preset == "default" ? (dark ? #354860 : #d5e8ff) : themed-accent.mix(themed-surface, dark ? 0.28 : 0.18); out property border: preset == "default" ? (dark ? #292a2b : #d9d9d6) : themed-border; out property border-subtle: preset == "default" ? (dark ? #232425 : #e6e6e2) : themed-border.mix(themed-surface, 0.58); out property control-border: preset == "default" ? (dark ? #3a3b3c : #d2d3d0) : themed-border.mix(themed-text, dark ? 0.76 : 0.88); out property input-border: preset == "default" ? (dark ? #383a3b : #cccdca) : themed-border.mix(themed-text, dark ? 0.80 : 0.86); out property title: preset == "default" ? (dark ? #f3f3f2 : #202120) : themed-text; out property heading: preset == "default" ? (dark ? #e4e4e2 : #30312f) : themed-text.mix(themed-page-bg, 0.94); out property body: preset == "default" ? (dark ? #c5c6c3 : #4b4c49) : themed-text.mix(themed-page-bg, 0.88); out property secondary: preset == "default" ? (dark ? #a0a19e : #72736f) : themed-text.mix(themed-page-bg, 0.80); out property muted: preset == "default" ? (dark ? #80817e : #888985) : themed-text.mix(themed-page-bg, 0.72); out property faint: preset == "default" ? (dark ? #737471 : #949591) : themed-text.mix(themed-page-bg, 0.68); out property disabled-bg: preset == "default" ? (dark ? #202122 : #ededeb) : themed-surface.mix(themed-text, dark ? 0.92 : 0.94); out property input-bg: preset == "default" ? (dark ? #1c1d1e : #ffffff) : themed-surface; out property placeholder: preset == "default" ? (dark ? #747572 : #90918d) : themed-text.mix(themed-surface, 0.68); out property destructive: #b73731; out property destructive-hover: #c8443d; out property destructive-pressed: #922d28; } export global ThemeUtilities { pure function linear-channel(channel: int) -> float { let value = channel / 255; return value <= 0.04045 ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4); } pure function luminance(value: color) -> float { return 0.2126 * self.linear-channel(value.red) + 0.7152 * self.linear-channel(value.green) + 0.0722 * self.linear-channel(value.blue); } public pure function contrast(first: color, second: color) -> float { let first-luminance = self.luminance(first); let second-luminance = self.luminance(second); return (max(first-luminance, second-luminance) + 0.05) / (min(first-luminance, second-luminance) + 0.05); } // Keep white where it meets AA, including the built-in button states. // Otherwise black provides at least 4.5:1 against any opaque color. public pure function contrasting-foreground(background: color) -> color { return self.contrast(white, background) >= 4.5 ? white : black; } // Rust handles strict #RRGGBB parsing and formatting because Slint has no // integer-to-hex string primitive. pure callback color-to-hex(color) -> string; pure callback parse-color(string, color) -> color; pure callback valid-color(string) -> bool; color-to-hex(value) => { return "#000000"; } parse-color(text, fallback) => { return fallback; } valid-color(text) => { return true; } }