|
| 1 | +import type { BrowserWindow, Rectangle } from 'electron' |
| 2 | + |
| 3 | +import { screen } from 'electron' |
| 4 | + |
| 5 | +export function currentDisplayBounds(window: BrowserWindow) { |
| 6 | + const bounds = window.getBounds() |
| 7 | + const nearbyDisplay = screen.getDisplayMatching(bounds) |
| 8 | + |
| 9 | + return nearbyDisplay.bounds |
| 10 | +} |
| 11 | + |
| 12 | +interface SizeActual { actual: number } |
| 13 | +interface SizePercentage { percentage: number } |
| 14 | +type Size = SizeActual | SizePercentage | number |
| 15 | + |
| 16 | +function evaluateSize(basedOn: number, size: Size) { |
| 17 | + if (typeof size === 'number') { |
| 18 | + return size |
| 19 | + } |
| 20 | + if ('actual' in size) { |
| 21 | + return size.actual |
| 22 | + } |
| 23 | + |
| 24 | + return Math.floor(basedOn * size.percentage) |
| 25 | +} |
| 26 | + |
| 27 | +/** |
| 28 | + * Breakpoint prefix Minimum width CSS |
| 29 | + * sm 40rem (640px) @media (width >= 40rem) { ... } |
| 30 | + * md 48rem (768px) @media (width >= 48rem) { ... } |
| 31 | + * lg 64rem (1024px) @media (width >= 64rem) { ... } |
| 32 | + * xl 80rem (1280px) @media (width >= 80rem) { ... } |
| 33 | + * 2xl 96rem (1536px) @media (width >= 96rem) { ... } |
| 34 | + * |
| 35 | + * Additional to tailwindcss defaults: |
| 36 | + * 3xl 112rem (1792px) @media (width >= 112rem) { ... } |
| 37 | + * 4xl 128rem (2048px) @media (width >= 128rem) { ... } |
| 38 | + * 5xl 144rem (2304px) @media (width >= 144rem) { ... } |
| 39 | + * 6xl 160rem (2560px) @media (width >= 160rem) { ... } |
| 40 | + * 7xl 176rem (2816px) @media (width >= 176rem) { ... } |
| 41 | + * 8xl 192rem (3072px) @media (width >= 192rem) { ... } |
| 42 | + * 9xl 208rem (3328px) @media (width >= 208rem) { ... } |
| 43 | + * 10xl 224rem (3584px) @media (width >= 224rem) { ... } |
| 44 | + */ |
| 45 | +export const tailwindBreakpoints = { |
| 46 | + 'sm': { min: 640, max: 767 }, |
| 47 | + 'md': { min: 768, max: 1023 }, |
| 48 | + 'lg': { min: 1024, max: 1279 }, |
| 49 | + 'xl': { min: 1280, max: 1535 }, |
| 50 | + '2xl': { min: 1536, max: 1791 }, |
| 51 | + '3xl': { min: 1792, max: 2047 }, |
| 52 | + '4xl': { min: 2048, max: 2303 }, |
| 53 | + '5xl': { min: 2304, max: 2559 }, |
| 54 | + '6xl': { min: 2560, max: 2815 }, |
| 55 | + '7xl': { min: 2816, max: 3071 }, |
| 56 | + '8xl': { min: 3072, max: 3327 }, |
| 57 | + '9xl': { min: 3328, max: 3583 }, |
| 58 | + '10xl': { min: 3584, max: Infinity }, |
| 59 | +} |
| 60 | + |
| 61 | +/** |
| 62 | + * Common screen resolution breakpoints. |
| 63 | + * Mainly for reference or if you want to target specific screen resolutions. |
| 64 | + * |
| 65 | + * - 720p HD 1280×720 |
| 66 | + * - 1080p FHD 1920×1080 |
| 67 | + * - 2K QHD 2560×1440 |
| 68 | + * - 4K UHD 3840×2160 |
| 69 | + * - 5K 5120×2880 |
| 70 | + * - 8K UHD 7680×4320 |
| 71 | + * |
| 72 | + * @see {@link https://en.wikipedia.org/wiki/Display_resolution#Common_display_resolutions} |
| 73 | + */ |
| 74 | +export const resolutionBreakpoints = { |
| 75 | + '720p': { min: 0, max: 1280 }, |
| 76 | + '1080p': { min: 1281, max: 1920 }, |
| 77 | + '2k': { min: 1921, max: 2560 }, |
| 78 | + '4k': { min: 2561, max: 3840 }, |
| 79 | + '5k': { min: 3841, max: 7680 }, |
| 80 | + '8k': { min: 7681, max: Infinity }, |
| 81 | +} |
| 82 | + |
| 83 | +/** |
| 84 | + * Achieve responsive sizes based on screen width breakpoints. |
| 85 | + * @see {@link https://tailwindcss.com/docs/responsive-design#overview} |
| 86 | + */ |
| 87 | +export function mapForBreakpoints< |
| 88 | + B extends Record<string, { min: number, max: number }> = typeof tailwindBreakpoints, |
| 89 | +>( |
| 90 | + basedOn: number, |
| 91 | + sizes: { [key in keyof B]?: number } | number, |
| 92 | + options?: { breakpoints: B }, |
| 93 | +) { |
| 94 | + if (typeof sizes === 'number') { |
| 95 | + return sizes |
| 96 | + } |
| 97 | + |
| 98 | + const breakpoints = options?.breakpoints ?? tailwindBreakpoints |
| 99 | + |
| 100 | + const matched = Object.entries(breakpoints).find(([, b]) => { |
| 101 | + return basedOn >= b.min && basedOn <= b.max |
| 102 | + }) |
| 103 | + |
| 104 | + if (matched) { |
| 105 | + const size = sizes[matched[0]] |
| 106 | + if (size) { |
| 107 | + return size |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + // Fallback: find nearest-least smallest breakpoint |
| 112 | + const sortedSizes = Object.entries(sizes) |
| 113 | + .map(([key, value]) => ({ key, value, min: breakpoints[key as keyof typeof breakpoints]?.min ?? 0 })) |
| 114 | + .sort((a, b) => b.min - a.min) // Sort descending by min width |
| 115 | + |
| 116 | + const fallback = sortedSizes.find(s => s.min <= basedOn) |
| 117 | + |
| 118 | + return fallback?.value ?? Object.values(sizes)?.[0] ?? 0 |
| 119 | +} |
| 120 | + |
| 121 | +/** |
| 122 | + * Calculate width based on options similar to how Web CSS does it. |
| 123 | + * |
| 124 | + * @param bounds |
| 125 | + * @param sizeOptions |
| 126 | + * @returns width in pixels |
| 127 | + */ |
| 128 | +export function widthFrom(bounds: Rectangle, sizeOptions: Size & { min?: Size, max?: Size }) { |
| 129 | + const val = evaluateSize(bounds.width, sizeOptions) |
| 130 | + const min = sizeOptions.min ? evaluateSize(bounds.width, sizeOptions.min) : undefined |
| 131 | + const max = sizeOptions.max ? evaluateSize(bounds.width, sizeOptions.max) : undefined |
| 132 | + |
| 133 | + if (min && val < min) { |
| 134 | + return min |
| 135 | + } |
| 136 | + |
| 137 | + if (max && val > max) { |
| 138 | + return max |
| 139 | + } |
| 140 | + |
| 141 | + return val |
| 142 | +} |
| 143 | + |
| 144 | +/** |
| 145 | + * Calculate height based on options similar to how Web CSS does it. |
| 146 | + * |
| 147 | + * @param bounds |
| 148 | + * @param sizeOptions |
| 149 | + * @returns height in pixels |
| 150 | + */ |
| 151 | +export function heightFrom(bounds: Rectangle, sizeOptions: Size & { min?: Size, max?: Size }) { |
| 152 | + const val = evaluateSize(bounds.height, sizeOptions) |
| 153 | + const min = sizeOptions.min ? evaluateSize(bounds.height, sizeOptions.min) : undefined |
| 154 | + const max = sizeOptions.max ? evaluateSize(bounds.height, sizeOptions.max) : undefined |
| 155 | + |
| 156 | + if (min && val < min) { |
| 157 | + return min |
| 158 | + } |
| 159 | + |
| 160 | + if (max && val > max) { |
| 161 | + return max |
| 162 | + } |
| 163 | + |
| 164 | + return val |
| 165 | +} |
0 commit comments