neiam
/neiam-devices
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
moire: give each time-of-day phase its own pattern as well as theme
be2c05b · Gabriel Morell · 2026-09-29 21:52
Message
{commit_body(@commit)}
Files changed
modified
README.md
+8
−5
@@ -69,11 +69,14 @@ Everything drifts slowly and never visibly repeats.
| 69 | 69 | Its colours come from the waxx themes (dark, light, her, afterdark, |
| 70 | 70 | forest, sky, clays, stones, blueprint): the ground is `base-100` |
| 71 | 71 | fading to `base-300`, the moire `accent`, the halftone `primary`. It |
| 72 | −can hold one theme, or follow the time of day through night, sunrise, | |
| 73 | −day and sunset themes, either by clock times or by the real sun at a | |
| 74 | −latitude and longitude. Between phases it blends by the sun's | |
| 75 | −elevation, turning the hue so the transitions pass through the purples | |
| 76 | −of a real twilight rather than grey. | |
| 72 | +can hold one theme and pattern, or follow the time of day through | |
| 73 | +night, sunrise, day and sunset, each phase with its own theme and | |
| 74 | +pattern (by default a starfield at night, bokeh at sunrise, moire and | |
| 75 | +halftone by day, moire at sunset), either by clock times or by the real | |
| 76 | +sun at a latitude and longitude. Between phases both blend by the sun's | |
| 77 | +elevation: the patterns crossfade, and the colours turn the hue so the | |
| 78 | +transitions pass through the purples of a real twilight rather than | |
| 79 | +grey. | |
| 77 | 80 | |
| 78 | 81 | A checkbox writes "NEIAM" in the bottom right corner, in the theme's |
| 79 | 82 | accent colour, in B612 Mono Bold at 12% of the screen height; the package pulls |
modified
moire/theme/contents/config/main.xml
+14
−1
@@ -57,7 +57,7 @@
| 57 | 57 | </entry> |
| 58 | 58 | |
| 59 | 59 | <entry name="ThemeMode" type="String"> |
| 60 | − <label>static, or sun to follow the time of day</label> | |
| 60 | + <label>static, or sun to follow the time of day with each phase's theme and pattern</label> | |
| 61 | 61 | <default>static</default> |
| 62 | 62 | </entry> |
| 63 | 63 | <entry name="Theme" type="String"> |
@@ -76,6 +76,19 @@
| 76 | 76 | <entry name="SunsetTheme" type="String"> |
| 77 | 77 | <default>her</default> |
| 78 | 78 | </entry> |
| 79 | + <!-- Each phase's pattern: moire, halftone, both, bokeh, or stars. --> | |
| 80 | + <entry name="NightPattern" type="String"> | |
| 81 | + <default>stars</default> | |
| 82 | + </entry> | |
| 83 | + <entry name="SunrisePattern" type="String"> | |
| 84 | + <default>bokeh</default> | |
| 85 | + </entry> | |
| 86 | + <entry name="DayPattern" type="String"> | |
| 87 | + <default>both</default> | |
| 88 | + </entry> | |
| 89 | + <entry name="SunsetPattern" type="String"> | |
| 90 | + <default>moire</default> | |
| 91 | + </entry> | |
| 79 | 92 | |
| 80 | 93 | <entry name="UseLocation" type="Bool"> |
| 81 | 94 | <label>Follow the real sun at Latitude/Longitude, rather than SunriseTime/SunsetTime</label> |
modified
moire/theme/contents/ui/Field.qml
+12
−3
@@ -11,9 +11,11 @@ Item {
| 11 | 11 | id: field |
| 12 | 12 | |
| 13 | 13 | // "moire", "halftone", "both" (moire over halftone), "bokeh", |
| 14 | − // "stars", or "alternate": fading between fadeFrom and fadeTo, which | |
| 15 | − // are any of the others, every few minutes. | |
| 14 | + // "stars", "alternate": fading between fadeFrom and fadeTo, which are | |
| 15 | + // any of the others, every few minutes, or "mix": the weighted blend | |
| 16 | + // of patterns in mixture, as [[pattern, weight], ...]. | |
| 16 | 17 | property string pattern: "both" |
| 18 | + property var mixture: [] | |
| 17 | 19 | property string fadeFrom: "moire" |
| 18 | 20 | property string fadeTo: "halftone" |
| 19 | 21 | property int bokehLights: 14 |
@@ -60,6 +62,12 @@ Item {
| 60 | 62 | } |
| 61 | 63 | |
| 62 | 64 | function amount(layer) { |
| 65 | + if (pattern === "mix") { | |
| 66 | + var sum = 0 | |
| 67 | + for (var i = 0; i < mixture.length; i++) | |
| 68 | + sum += layers(mixture[i][0])[layer] * mixture[i][1] | |
| 69 | + return sum | |
| 70 | + } | |
| 63 | 71 | if (pattern !== "alternate") |
| 64 | 72 | return layers(pattern)[layer] |
| 65 | 73 | var t = 0.5 + 0.5 * sweep(360, 0) |
@@ -135,6 +143,7 @@ Item {
| 135 | 143 | // B612 Mono comes from fonts-b612 / firstyear-b612-fonts, which the |
| 136 | 144 | // package depends on. |
| 137 | 145 | Text { |
| 146 | + id: mark | |
| 138 | 147 | visible: field.wordmark |
| 139 | 148 | anchors { right: parent.right; bottom: parent.bottom; margins: field.height * 0.04 } |
| 140 | 149 | text: "NEIAM" |
@@ -142,7 +151,7 @@ Item {
| 142 | 151 | font { family: "B612 Mono"; bold: true; pixelSize: Math.max(1, Math.round(field.height * 0.12)) } |
| 143 | 152 | // Sit the capitals, not the line box, on the margin. |
| 144 | 153 | anchors.bottomMargin: field.height * 0.04 - fontMetrics.descent |
| 145 | − FontMetrics { id: fontMetrics; font: parent.font } | |
| 154 | + FontMetrics { id: fontMetrics; font: mark.font } | |
| 146 | 155 | } |
| 147 | 156 | |
| 148 | 157 | Timer { |
modified
moire/theme/contents/ui/config.qml
+143
−120
@@ -29,6 +29,10 @@ Kirigami.FormLayout {
| 29 | 29 | property string cfg_SunriseTheme |
| 30 | 30 | property string cfg_DayTheme |
| 31 | 31 | property string cfg_SunsetTheme |
| 32 | + property string cfg_NightPattern | |
| 33 | + property string cfg_SunrisePattern | |
| 34 | + property string cfg_DayPattern | |
| 35 | + property string cfg_SunsetPattern | |
| 32 | 36 | property bool cfg_UseLocation |
| 33 | 37 | property double cfg_Latitude |
| 34 | 38 | property double cfg_Longitude |
@@ -45,9 +49,11 @@ Kirigami.FormLayout {
| 45 | 49 | ] |
| 46 | 50 | readonly property var patterns: layers.concat([{ value: "alternate", text: "Fade between two…" }]) |
| 47 | 51 | |
| 48 | − readonly property bool fading: cfg_Pattern === "alternate" | |
| 49 | − readonly property bool showsBokeh: cfg_Pattern === "bokeh" | |
| 50 | − || (fading && (cfg_FadeFrom === "bokeh" || cfg_FadeTo === "bokeh")) | |
| 52 | + readonly property bool followsSun: cfg_ThemeMode === "sun" | |
| 53 | + readonly property bool fading: !followsSun && cfg_Pattern === "alternate" | |
| 54 | + readonly property bool showsBokeh: followsSun | |
| 55 | + ? [cfg_NightPattern, cfg_SunrisePattern, cfg_DayPattern, cfg_SunsetPattern].indexOf("bokeh") >= 0 | |
| 56 | + : cfg_Pattern === "bokeh" || (fading && (cfg_FadeFrom === "bokeh" || cfg_FadeTo === "bokeh")) | |
| 51 | 57 | |
| 52 | 58 | component PatternBox: QQC2.ComboBox { |
| 53 | 59 | property var choices |
@@ -68,6 +74,17 @@ Kirigami.FormLayout {
| 68 | 74 | onActivated: index => picked(Themes.names[index]) |
| 69 | 75 | } |
| 70 | 76 | |
| 77 | + // One phase of the day: its theme and its pattern side by side. | |
| 78 | + component PhaseRow: RowLayout { | |
| 79 | + property string theme | |
| 80 | + property string pattern | |
| 81 | + signal themePicked(string name) | |
| 82 | + signal patternPicked(string value) | |
| 83 | + visible: root.followsSun | |
| 84 | + ThemeBox { value: parent.theme; onPicked: name => parent.themePicked(name) } | |
| 85 | + PatternBox { choices: root.layers; value: parent.pattern; onPicked: v => parent.patternPicked(v) } | |
| 86 | + } | |
| 87 | + | |
| 71 | 88 | component TimeField: QQC2.TextField { |
| 72 | 89 | inputMask: "99:99" |
| 73 | 90 | validator: RegularExpressionValidator { regularExpression: /([01]\d|2[0-3]):[0-5]\d/ } |
@@ -81,10 +98,32 @@ Kirigami.FormLayout {
| 81 | 98 | onEditingFinished: edited(Number(text)) |
| 82 | 99 | } |
| 83 | 100 | |
| 84 | − // ------------------------------------------------------------ pattern | |
| 101 | + // --------------------------------------------------------------- mode | |
| 102 | + | |
| 103 | + QQC2.RadioButton { | |
| 104 | + Kirigami.FormData.label: "Look:" | |
| 105 | + text: "Always the same" | |
| 106 | + checked: !root.followsSun | |
| 107 | + onToggled: if (checked) root.cfg_ThemeMode = "static" | |
| 108 | + } | |
| 109 | + QQC2.RadioButton { | |
| 110 | + text: "Follow the time of day" | |
| 111 | + checked: root.followsSun | |
| 112 | + onToggled: if (checked) root.cfg_ThemeMode = "sun" | |
| 113 | + } | |
| 114 | + | |
| 115 | + // ------------------------------------------------------------- static | |
| 116 | + | |
| 117 | + ThemeBox { | |
| 118 | + Kirigami.FormData.label: "Theme:" | |
| 119 | + visible: !root.followsSun | |
| 120 | + value: root.cfg_Theme | |
| 121 | + onPicked: name => root.cfg_Theme = name | |
| 122 | + } | |
| 85 | 123 | |
| 86 | 124 | PatternBox { |
| 87 | 125 | Kirigami.FormData.label: "Pattern:" |
| 126 | + visible: !root.followsSun | |
| 88 | 127 | choices: root.patterns |
| 89 | 128 | value: root.cfg_Pattern |
| 90 | 129 | onPicked: v => root.cfg_Pattern = v |
@@ -106,113 +145,31 @@ Kirigami.FormLayout {
| 106 | 145 | onPicked: v => root.cfg_FadeTo = v |
| 107 | 146 | } |
| 108 | 147 | |
| 109 | − QQC2.SpinBox { | |
| 110 | − Kirigami.FormData.label: "Bokeh lights:" | |
| 111 | − visible: root.showsBokeh | |
| 112 | − from: 4; to: 64 | |
| 113 | − value: root.cfg_BokehLights | |
| 114 | − onValueModified: root.cfg_BokehLights = value | |
| 115 | − } | |
| 116 | − | |
| 117 | − RowLayout { | |
| 118 | − Kirigami.FormData.label: "Speed:" | |
| 119 | − QQC2.Slider { | |
| 120 | − id: speed | |
| 121 | − from: 0; to: 4; stepSize: 0.25 | |
| 122 | − value: root.cfg_Speed | |
| 123 | − onMoved: root.cfg_Speed = value | |
| 124 | − } | |
| 125 | − QQC2.Label { text: speed.value === 0 ? "still" : speed.value + "×" } | |
| 126 | − } | |
| 127 | − | |
| 128 | − QQC2.SpinBox { | |
| 129 | − Kirigami.FormData.label: "Frame rate:" | |
| 130 | − from: 5; to: 60 | |
| 131 | − value: root.cfg_FrameRate | |
| 132 | − onValueModified: root.cfg_FrameRate = value | |
| 133 | − enabled: root.cfg_Speed > 0 | |
| 134 | − } | |
| 135 | − | |
| 136 | − QQC2.CheckBox { | |
| 137 | − Kirigami.FormData.label: "Effects:" | |
| 138 | − text: "Light glinting across the pattern" | |
| 139 | − checked: root.cfg_Glints | |
| 140 | − onToggled: root.cfg_Glints = checked | |
| 141 | − } | |
| 142 | − QQC2.CheckBox { | |
| 143 | − text: "Breathing" | |
| 144 | − checked: root.cfg_Breathing | |
| 145 | − onToggled: root.cfg_Breathing = checked | |
| 146 | − } | |
| 147 | − QQC2.CheckBox { | |
| 148 | − text: "Large lights drifting past in front" | |
| 149 | − checked: root.cfg_ForegroundBokeh | |
| 150 | − onToggled: root.cfg_ForegroundBokeh = checked | |
| 151 | − } | |
| 152 | − QQC2.CheckBox { | |
| 153 | − text: "Film grain" | |
| 154 | − checked: root.cfg_Grain | |
| 155 | − onToggled: root.cfg_Grain = checked | |
| 156 | − } | |
| 157 | − QQC2.CheckBox { | |
| 158 | − text: "Dither (smooths banding in gradients)" | |
| 159 | − checked: root.cfg_Dither | |
| 160 | − onToggled: root.cfg_Dither = checked | |
| 161 | − } | |
| 162 | − | |
| 163 | − QQC2.CheckBox { | |
| 164 | − Kirigami.FormData.label: "Wordmark:" | |
| 165 | − text: "Write NEIAM in the corner" | |
| 166 | − checked: root.cfg_Wordmark | |
| 167 | − onToggled: root.cfg_Wordmark = checked | |
| 168 | − } | |
| 169 | − | |
| 170 | − // ------------------------------------------------------------- theme | |
| 171 | − | |
| 172 | − Item { Kirigami.FormData.isSection: true } | |
| 173 | − | |
| 174 | − QQC2.RadioButton { | |
| 175 | − Kirigami.FormData.label: "Theme:" | |
| 176 | − text: "Always the same" | |
| 177 | − checked: root.cfg_ThemeMode !== "sun" | |
| 178 | − onToggled: if (checked) root.cfg_ThemeMode = "static" | |
| 179 | − } | |
| 180 | − QQC2.RadioButton { | |
| 181 | − text: "Follow the time of day" | |
| 182 | − checked: root.cfg_ThemeMode === "sun" | |
| 183 | − onToggled: if (checked) root.cfg_ThemeMode = "sun" | |
| 184 | − } | |
| 185 | − | |
| 186 | − ThemeBox { | |
| 187 | − Kirigami.FormData.label: "Theme:" | |
| 188 | − visible: root.cfg_ThemeMode !== "sun" | |
| 189 | − value: root.cfg_Theme | |
| 190 | − onPicked: name => root.cfg_Theme = name | |
| 191 | − } | |
| 148 | + // ---------------------------------------------------- time of day | |
| 192 | 149 | |
| 193 | − ThemeBox { | |
| 150 | + PhaseRow { | |
| 194 | 151 | Kirigami.FormData.label: "Night:" |
| 195 | − visible: root.cfg_ThemeMode === "sun" | |
| 196 | − value: root.cfg_NightTheme | |
| 197 | − onPicked: name => root.cfg_NightTheme = name | |
| 152 | + theme: root.cfg_NightTheme; pattern: root.cfg_NightPattern | |
| 153 | + onThemePicked: name => root.cfg_NightTheme = name | |
| 154 | + onPatternPicked: v => root.cfg_NightPattern = v | |
| 198 | 155 | } |
| 199 | − ThemeBox { | |
| 156 | + PhaseRow { | |
| 200 | 157 | Kirigami.FormData.label: "Sunrise:" |
| 201 | − visible: root.cfg_ThemeMode === "sun" | |
| 202 | − value: root.cfg_SunriseTheme | |
| 203 | − onPicked: name => root.cfg_SunriseTheme = name | |
| 158 | + theme: root.cfg_SunriseTheme; pattern: root.cfg_SunrisePattern | |
| 159 | + onThemePicked: name => root.cfg_SunriseTheme = name | |
| 160 | + onPatternPicked: v => root.cfg_SunrisePattern = v | |
| 204 | 161 | } |
| 205 | − ThemeBox { | |
| 162 | + PhaseRow { | |
| 206 | 163 | Kirigami.FormData.label: "Day:" |
| 207 | − visible: root.cfg_ThemeMode === "sun" | |
| 208 | − value: root.cfg_DayTheme | |
| 209 | − onPicked: name => root.cfg_DayTheme = name | |
| 164 | + theme: root.cfg_DayTheme; pattern: root.cfg_DayPattern | |
| 165 | + onThemePicked: name => root.cfg_DayTheme = name | |
| 166 | + onPatternPicked: v => root.cfg_DayPattern = v | |
| 210 | 167 | } |
| 211 | − ThemeBox { | |
| 168 | + PhaseRow { | |
| 212 | 169 | Kirigami.FormData.label: "Sunset:" |
| 213 | − visible: root.cfg_ThemeMode === "sun" | |
| 214 | − value: root.cfg_SunsetTheme | |
| 215 | − onPicked: name => root.cfg_SunsetTheme = name | |
| 170 | + theme: root.cfg_SunsetTheme; pattern: root.cfg_SunsetPattern | |
| 171 | + onThemePicked: name => root.cfg_SunsetTheme = name | |
| 172 | + onPatternPicked: v => root.cfg_SunsetPattern = v | |
| 216 | 173 | } |
| 217 | 174 | |
| 218 | 175 | // ---------------------------------------------------------- the sun |
@@ -254,32 +211,98 @@ Kirigami.FormLayout {
| 254 | 211 | QQC2.Label { |
| 255 | 212 | id: now |
| 256 | 213 | Kirigami.FormData.label: "Now:" |
| 257 | − visible: root.cfg_ThemeMode === "sun" | |
| 214 | + visible: root.followsSun | |
| 215 | + | |
| 216 | + // Bumped each minute; the binding below also re-reads every | |
| 217 | + // setting describe() touches, so edits show straight away. | |
| 218 | + property int tick: 0 | |
| 219 | + Timer { | |
| 220 | + interval: 60000; running: now.visible; repeat: true | |
| 221 | + onTriggered: now.tick++ | |
| 222 | + } | |
| 258 | 223 | |
| 259 | 224 | function describe() { |
| 260 | − var p = Schedule.palette({ | |
| 225 | + var cfg = { | |
| 261 | 226 | ThemeMode: "sun", UseLocation: root.cfg_UseLocation, |
| 262 | 227 | Latitude: root.cfg_Latitude, Longitude: root.cfg_Longitude, |
| 263 | 228 | SunriseTime: root.cfg_SunriseTime, SunsetTime: root.cfg_SunsetTime, |
| 264 | 229 | NightTheme: root.cfg_NightTheme, SunriseTheme: root.cfg_SunriseTheme, |
| 265 | 230 | DayTheme: root.cfg_DayTheme, SunsetTheme: root.cfg_SunsetTheme, |
| 266 | − }, new Date()) | |
| 267 | − return p.label + (root.cfg_UseLocation | |
| 268 | − ? ", sun at " + p.elevation.toFixed(1) + "°" : "") | |
| 231 | + NightPattern: root.cfg_NightPattern, SunrisePattern: root.cfg_SunrisePattern, | |
| 232 | + DayPattern: root.cfg_DayPattern, SunsetPattern: root.cfg_SunsetPattern, | |
| 233 | + } | |
| 234 | + var p = Schedule.palette(cfg, new Date()) | |
| 235 | + var pattern = root.layers.find(l => l.value === cfg[Schedule.key(p.label, "Pattern")]) | |
| 236 | + return p.label + ": " + cfg[Schedule.key(p.label, "Theme")] | |
| 237 | + + ", " + (pattern ? pattern.text.toLowerCase() : "") | |
| 238 | + + (root.cfg_UseLocation ? " (sun at " + p.elevation.toFixed(1) + "°)" : "") | |
| 269 | 239 | } |
| 270 | 240 | |
| 271 | − text: describe() | |
| 272 | − Timer { | |
| 273 | − interval: 60000; running: now.visible; repeat: true | |
| 274 | − onTriggered: now.text = now.describe() | |
| 275 | − } | |
| 276 | − Connections { | |
| 277 | − target: root | |
| 278 | − function onCfg_UseLocationChanged() { now.text = now.describe() } | |
| 279 | − function onCfg_LatitudeChanged() { now.text = now.describe() } | |
| 280 | − function onCfg_LongitudeChanged() { now.text = now.describe() } | |
| 281 | − function onCfg_SunriseTimeChanged() { now.text = now.describe() } | |
| 282 | − function onCfg_SunsetTimeChanged() { now.text = now.describe() } | |
| 241 | + text: { tick; return describe() } | |
| 242 | + } | |
| 243 | + | |
| 244 | + // ------------------------------------------------------------ shared | |
| 245 | + | |
| 246 | + Item { Kirigami.FormData.isSection: true } | |
| 247 | + | |
| 248 | + QQC2.SpinBox { | |
| 249 | + Kirigami.FormData.label: "Bokeh lights:" | |
| 250 | + visible: root.showsBokeh | |
| 251 | + from: 4; to: 64 | |
| 252 | + value: root.cfg_BokehLights | |
| 253 | + onValueModified: root.cfg_BokehLights = value | |
| 254 | + } | |
| 255 | + | |
| 256 | + RowLayout { | |
| 257 | + Kirigami.FormData.label: "Speed:" | |
| 258 | + QQC2.Slider { | |
| 259 | + id: speed | |
| 260 | + from: 0; to: 4; stepSize: 0.25 | |
| 261 | + value: root.cfg_Speed | |
| 262 | + onMoved: root.cfg_Speed = value | |
| 283 | 263 | } |
| 264 | + QQC2.Label { text: speed.value === 0 ? "still" : speed.value + "×" } | |
| 265 | + } | |
| 266 | + | |
| 267 | + QQC2.SpinBox { | |
| 268 | + Kirigami.FormData.label: "Frame rate:" | |
| 269 | + from: 5; to: 60 | |
| 270 | + value: root.cfg_FrameRate | |
| 271 | + onValueModified: root.cfg_FrameRate = value | |
| 272 | + enabled: root.cfg_Speed > 0 | |
| 273 | + } | |
| 274 | + | |
| 275 | + QQC2.CheckBox { | |
| 276 | + Kirigami.FormData.label: "Effects:" | |
| 277 | + text: "Light glinting across the pattern" | |
| 278 | + checked: root.cfg_Glints | |
| 279 | + onToggled: root.cfg_Glints = checked | |
| 280 | + } | |
| 281 | + QQC2.CheckBox { | |
| 282 | + text: "Breathing" | |
| 283 | + checked: root.cfg_Breathing | |
| 284 | + onToggled: root.cfg_Breathing = checked | |
| 285 | + } | |
| 286 | + QQC2.CheckBox { | |
| 287 | + text: "Large lights drifting past in front" | |
| 288 | + checked: root.cfg_ForegroundBokeh | |
| 289 | + onToggled: root.cfg_ForegroundBokeh = checked | |
| 290 | + } | |
| 291 | + QQC2.CheckBox { | |
| 292 | + text: "Film grain" | |
| 293 | + checked: root.cfg_Grain | |
| 294 | + onToggled: root.cfg_Grain = checked | |
| 295 | + } | |
| 296 | + QQC2.CheckBox { | |
| 297 | + text: "Dither (smooths banding in gradients)" | |
| 298 | + checked: root.cfg_Dither | |
| 299 | + onToggled: root.cfg_Dither = checked | |
| 300 | + } | |
| 301 | + | |
| 302 | + QQC2.CheckBox { | |
| 303 | + Kirigami.FormData.label: "Wordmark:" | |
| 304 | + text: "Write NEIAM in the corner" | |
| 305 | + checked: root.cfg_Wordmark | |
| 306 | + onToggled: root.cfg_Wordmark = checked | |
| 284 | 307 | } |
| 285 | 308 | } |
modified
moire/theme/contents/ui/main.qml
+4
−1
@@ -28,7 +28,10 @@ WallpaperItem {
| 28 | 28 | |
| 29 | 29 | Field { |
| 30 | 30 | anchors.fill: parent |
| 31 | − pattern: root.cfg.Pattern | |
| 31 | + // Following the sun, the phases' patterns blend as their themes | |
| 32 | + // do; the mixture moves once a minute and Field eases the steps. | |
| 33 | + pattern: root.cfg.ThemeMode === "sun" ? "mix" : root.cfg.Pattern | |
| 34 | + mixture: root.scheme.patterns | |
| 32 | 35 | fadeFrom: root.cfg.FadeFrom |
| 33 | 36 | fadeTo: root.cfg.FadeTo |
| 34 | 37 | bokehLights: root.cfg.BokehLights |
modified
moire/theme/contents/ui/schedule.js
+27
−18
@@ -1,5 +1,5 @@
| 1 | −// Which palette to paint with: a fixed theme, or one that follows the | |
| 2 | −// sun through night, sunrise, day and sunset. | |
| 1 | +// What to paint: a fixed theme and pattern, or ones that follow the sun | |
| 2 | +// through night, sunrise, day and sunset, each phase with its own. | |
| 3 | 3 | // |
| 4 | 4 | // Following the sun blends between the four phase themes by the sun's |
| 5 | 5 | // elevation, so the change is continuous rather than a switch at fixed |
@@ -113,33 +113,42 @@ function smooth(t) {
| 113 | 113 | return t * t * (3 - 2 * t) |
| 114 | 114 | } |
| 115 | 115 | |
| 116 | −// Night below -12 degrees (nautical dusk), the twilight theme across | |
| 117 | −// -4 to 2, day above 10, crossfading in between. Whether the twilight | |
| 118 | −// theme is sunrise's or sunset's depends on which way the sun is going. | |
| 119 | −function weights(elevation, rising, night, sunrise, day, sunset) { | |
| 120 | − var twilight = rising ? sunrise : sunset | |
| 116 | +// Night below -12 degrees (nautical dusk), twilight across -4 to 2, | |
| 117 | +// day above 10, crossfading in between. Whether twilight is sunrise or | |
| 118 | +// sunset depends on which way the sun is going. Returns the phases in | |
| 119 | +// play, as [["night", weight], ...]. | |
| 120 | +function weights(elevation, rising) { | |
| 121 | + var twilight = rising ? "sunrise" : "sunset" | |
| 121 | 122 | if (elevation < -12) |
| 122 | − return { label: "night", weights: [[night, 1]] } | |
| 123 | + return { label: "night", weights: [["night", 1]] } | |
| 123 | 124 | if (elevation < -4) { |
| 124 | 125 | var t = smooth((elevation + 12) / 8) |
| 125 | − return { label: t < 0.5 ? "night" : (rising ? "sunrise" : "sunset"), | |
| 126 | − weights: [[night, 1 - t], [twilight, t]] } | |
| 126 | + return { label: t < 0.5 ? "night" : twilight, | |
| 127 | + weights: [["night", 1 - t], [twilight, t]] } | |
| 127 | 128 | } |
| 128 | 129 | if (elevation < 2) |
| 129 | − return { label: rising ? "sunrise" : "sunset", weights: [[twilight, 1]] } | |
| 130 | + return { label: twilight, weights: [[twilight, 1]] } | |
| 130 | 131 | if (elevation < 10) { |
| 131 | 132 | var u = smooth((elevation - 2) / 8) |
| 132 | − return { label: u < 0.5 ? (rising ? "sunrise" : "sunset") : "day", | |
| 133 | − weights: [[twilight, 1 - u], [day, u]] } | |
| 133 | + return { label: u < 0.5 ? twilight : "day", | |
| 134 | + weights: [[twilight, 1 - u], ["day", u]] } | |
| 134 | 135 | } |
| 135 | − return { label: "day", weights: [[day, 1]] } | |
| 136 | + return { label: "day", weights: [["day", 1]] } | |
| 137 | +} | |
| 138 | + | |
| 139 | +// The config key for a phase's setting: ("sunrise", "Theme") is | |
| 140 | +// SunriseTheme. | |
| 141 | +function key(phase, what) { | |
| 142 | + return phase.charAt(0).toUpperCase() + phase.slice(1) + what | |
| 136 | 143 | } |
| 137 | 144 | |
| 138 | 145 | // cfg is the wallpaper configuration (or anything with the same keys). |
| 139 | −// Returns { base, deep, ink, tone, label, elevation }. | |
| 146 | +// Returns { base, deep, ink, tone, patterns, label, elevation }, where | |
| 147 | +// patterns is [[pattern, weight], ...] for Field's "mix" pattern. | |
| 140 | 148 | function palette(cfg, date) { |
| 141 | 149 | if (cfg.ThemeMode !== "sun") { |
| 142 | 150 | var fixed = blend([[cfg.Theme, 1]]) |
| 151 | + fixed.patterns = [[cfg.Pattern, 1]] | |
| 143 | 152 | fixed.label = cfg.Theme |
| 144 | 153 | return fixed |
| 145 | 154 | } |
@@ -150,9 +159,9 @@ function palette(cfg, date) {
| 150 | 159 | var elevation = elevationAt(date) |
| 151 | 160 | var rising = elevationAt(new Date(date.getTime() + 600000)) > elevation |
| 152 | 161 | |
| 153 | − var phase = weights(elevation, rising, | |
| 154 | − cfg.NightTheme, cfg.SunriseTheme, cfg.DayTheme, cfg.SunsetTheme) | |
| 155 | − var result = blend(phase.weights) | |
| 162 | + var phase = weights(elevation, rising) | |
| 163 | + var result = blend(phase.weights.map(function (w) { return [cfg[key(w[0], "Theme")], w[1]] })) | |
| 164 | + result.patterns = phase.weights.map(function (w) { return [cfg[key(w[0], "Pattern")], w[1]] }) | |
| 156 | 165 | result.label = phase.label |
| 157 | 166 | result.elevation = elevation |
| 158 | 167 | return result |
Parents: c2e4eb1