a lightweight, interval-based utility to combat digital strain through "Ma" (intentional pauses) for the eyes and body.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(ui): move components to separate files

+336 -327
+127
ui/components/buttons.slint
··· 1 + import { Theme } from "../theme.slint"; 2 + 3 + // A ghost button — semi-transparent surface, thin line border 4 + export component PaperButton { 5 + in property <string> text; 6 + callback clicked; 7 + 8 + height: 28px; 9 + min-width: 100px; 10 + preferred-width: 160px; 11 + 12 + ta := TouchArea { 13 + clicked => { 14 + root.clicked(); 15 + } 16 + } 17 + 18 + Rectangle { 19 + border-radius: 6px; 20 + background: Theme.surface; 21 + border-width: 1px; 22 + border-color: Theme.line; 23 + 24 + Text { 25 + text: root.text; 26 + font-size: 12px; 27 + color: Theme.ink; 28 + horizontal-alignment: center; 29 + vertical-alignment: center; 30 + } 31 + } 32 + } 33 + 34 + // Primary filled button (inverted: ink bg, cream text in light; cream bg, charcoal text in dark) 35 + export component PaperPrimaryButton { 36 + in property <string> text; 37 + callback clicked; 38 + 39 + height: 30px; 40 + min-width: 72px; 41 + 42 + ta := TouchArea { 43 + clicked => { 44 + root.clicked(); 45 + } 46 + } 47 + 48 + Rectangle { 49 + border-radius: 6px; 50 + background: ta.has-hover ? Theme.btn-bg-hov : Theme.btn-bg; 51 + animate background { duration: 120ms; } 52 + 53 + Text { 54 + text: root.text; 55 + font-size: 12px; 56 + font-weight: 600; 57 + color: Theme.btn-fg; 58 + horizontal-alignment: center; 59 + vertical-alignment: center; 60 + } 61 + } 62 + } 63 + 64 + // Cancel-style secondary button 65 + export component PaperSecondaryButton { 66 + in property <string> text; 67 + callback clicked; 68 + 69 + height: 30px; 70 + min-width: 72px; 71 + 72 + ta := TouchArea { 73 + clicked => { 74 + root.clicked(); 75 + } 76 + } 77 + 78 + Rectangle { 79 + border-radius: 6px; 80 + background: transparent; 81 + border-width: 1px; 82 + border-color: Theme.line; 83 + 84 + Text { 85 + text: root.text; 86 + font-size: 12px; 87 + color: Theme.ink; 88 + horizontal-alignment: center; 89 + vertical-alignment: center; 90 + } 91 + } 92 + } 93 + 94 + // Settings tab button 95 + export component TabButton { 96 + in property <string> text; 97 + in property <bool> active: false; 98 + callback clicked; 99 + 100 + height: 40px; 101 + min-width: 64px; 102 + 103 + TouchArea { 104 + clicked => { 105 + root.clicked(); 106 + } 107 + } 108 + 109 + VerticalLayout { 110 + padding-bottom: 0px; 111 + 112 + Text { 113 + text: root.text; 114 + font-size: 13px; 115 + font-weight: root.active ? 600 : 400; 116 + color: root.active ? Theme.ink : Theme.ink-mid; 117 + horizontal-alignment: left; 118 + vertical-alignment: center; 119 + } 120 + 121 + // Active indicator 122 + Rectangle { 123 + height: 2px; 124 + background: root.active ? Theme.ink : transparent; 125 + } 126 + } 127 + }
+152
ui/components/input_fields.slint
··· 1 + import { Theme } from "../theme.slint"; 2 + 3 + export component NumberField { 4 + in-out property <int> value: 0; 5 + in property <int> minimum: 0; 6 + in property <int> maximum: 100; 7 + in property <string> field-label: ""; 8 + 9 + callback edited(int); 10 + 11 + height: 28px; 12 + min-width: 80px; 13 + accessible-role: spinbox; 14 + accessible-label: root.field-label; 15 + accessible-value: "\{root.value}"; 16 + accessible-value-minimum: root.minimum; 17 + accessible-value-maximum: root.maximum; 18 + 19 + Rectangle { 20 + border-radius: 6px; 21 + background: Theme.surface; 22 + border-width: 1px; 23 + border-color: Theme.line; 24 + clip: true; 25 + 26 + HorizontalLayout { 27 + btn-minus := TouchArea { 28 + width: 26px; 29 + accessible-role: button; 30 + accessible-label: "Decrease " + root.field-label; 31 + clicked => { 32 + if root.value > root.minimum { 33 + root.value = root.value - 1; 34 + root.edited(root.value); 35 + } 36 + } 37 + 38 + Rectangle { 39 + background: btn-minus.has-hover ? Theme.tint : transparent; 40 + animate background { duration: 120ms; } 41 + } 42 + 43 + Text { 44 + text: "−"; 45 + font-size: 13px; 46 + color: root.value <= root.minimum ? Theme.ink-dis : Theme.ink-lo; 47 + horizontal-alignment: center; 48 + vertical-alignment: center; 49 + animate color { duration: 120ms; } 50 + } 51 + } 52 + 53 + Rectangle { 54 + width: 1px; 55 + background: Theme.line; 56 + } 57 + 58 + Text { 59 + text: root.value; 60 + font-size: 12px; 61 + font-weight: 500; 62 + color: Theme.ink; 63 + horizontal-alignment: center; 64 + vertical-alignment: center; 65 + horizontal-stretch: 1; 66 + } 67 + 68 + Rectangle { 69 + width: 1px; 70 + background: Theme.line; 71 + } 72 + 73 + btn-plus := TouchArea { 74 + width: 26px; 75 + accessible-role: button; 76 + accessible-label: "Increase " + root.field-label; 77 + clicked => { 78 + if root.value < root.maximum { 79 + root.value = root.value + 1; 80 + root.edited(root.value); 81 + } 82 + } 83 + 84 + Rectangle { 85 + background: btn-plus.has-hover ? Theme.tint : transparent; 86 + animate background { duration: 120ms; } 87 + } 88 + 89 + Text { 90 + text: "+"; 91 + font-size: 13px; 92 + color: root.value >= root.maximum ? Theme.ink-dis : Theme.ink-lo; 93 + horizontal-alignment: center; 94 + vertical-alignment: center; 95 + animate color { duration: 120ms; } 96 + } 97 + } 98 + } 99 + } 100 + } 101 + 102 + // Styled text input 103 + export component PaperInput { 104 + in-out property <string> text: ""; 105 + in property <string> placeholder-text: ""; 106 + in property <string> field-label: ""; 107 + callback edited(string); 108 + 109 + height: 32px; 110 + accessible-role: text-input; 111 + accessible-label: root.field-label; 112 + accessible-value: root.text; 113 + 114 + Rectangle { 115 + border-radius: 6px; 116 + background: ti.has-focus ? Theme.tint-soft : Theme.surface-inp; 117 + border-width: 1px; 118 + border-color: ti.has-focus ? Theme.accent-muted : Theme.line; 119 + animate background { duration: 120ms; } 120 + animate border-color { duration: 120ms; } 121 + clip: true; 122 + 123 + // Placeholder text when empty and unfocused 124 + if root.text == "" && !ti.has-focus: Text { 125 + x: 10px; 126 + text: root.placeholder-text; 127 + font-size: 13px; 128 + color: Theme.ink-dim; 129 + vertical-alignment: center; 130 + } 131 + 132 + HorizontalLayout { 133 + padding-left: 10px; 134 + padding-right: 10px; 135 + 136 + ti := TextInput { 137 + horizontal-stretch: 1; 138 + text <=> root.text; 139 + font-size: 13px; 140 + font-weight: 500; 141 + color: Theme.ink; 142 + horizontal-alignment: left; 143 + selection-background-color: Theme.accent-sel; 144 + selection-foreground-color: Theme.ink; 145 + vertical-alignment: center; 146 + edited => { 147 + root.edited(self.text); 148 + } 149 + } 150 + } 151 + } 152 + }
+45
ui/components/widgets.slint
··· 1 + import { Theme } from "../theme.slint"; 2 + 3 + export component PaperDivider { 4 + height: 1px; 5 + Rectangle { 6 + background: Theme.line; 7 + } 8 + } 9 + 10 + export component PaperToggle { 11 + in-out property <bool> checked: false; 12 + callback toggled(bool); 13 + 14 + width: 38px; 15 + height: 22px; 16 + 17 + TouchArea { 18 + clicked => { 19 + root.checked = !root.checked; 20 + root.toggled(root.checked); 21 + } 22 + } 23 + 24 + Rectangle { 25 + border-radius: 11px; 26 + background: root.checked ? Theme.accent : Theme.toggle-off; 27 + animate background { duration: 180ms; } 28 + 29 + Rectangle { 30 + x: root.checked ? 18px : 2px; 31 + y: 2px; 32 + width: 18px; 33 + height: 18px; 34 + border-radius: 9px; 35 + background: white; 36 + drop-shadow-color: #00000026; 37 + drop-shadow-blur: 3px; 38 + drop-shadow-offset-y: 1px; 39 + animate x { 40 + duration: 180ms; 41 + easing: ease-out; 42 + } 43 + } 44 + } 45 + }
+12 -327
ui/settings.slint
··· 1 1 import { SpinBox, Slider, LineEdit, ScrollView } from "std-widgets.slint"; 2 2 import { Theme } from "theme.slint"; 3 + import { PaperDivider, PaperToggle } from "components/widgets.slint"; 4 + import { NumberField, PaperInput } from "components/input_fields.slint"; 5 + import { 6 + PaperButton, 7 + PaperPrimaryButton, 8 + PaperSecondaryButton, 9 + TabButton, 10 + } from "components/buttons.slint"; 3 11 import "../assets/fonts/Nunito-Regular.ttf"; 4 12 import "../assets/fonts/Nunito-Medium.ttf"; 5 13 import "../assets/fonts/Nunito-SemiBold.ttf"; ··· 7 15 import "../assets/fonts/ShipporiMincho-Regular.ttf"; 8 16 import "../assets/fonts/ShipporiMincho-Bold.ttf"; 9 17 10 - // ── Primitives ─────────────────────────────────────────────────────────────── 11 - component PaperToggle { 12 - in-out property <bool> checked: false; 13 - callback toggled(bool); 14 - 15 - width: 38px; 16 - height: 22px; 17 - 18 - TouchArea { 19 - clicked => { 20 - root.checked = !root.checked; 21 - root.toggled(root.checked); 22 - } 23 - } 24 - 25 - Rectangle { 26 - border-radius: 11px; 27 - background: root.checked ? Theme.accent : Theme.toggle-off; 28 - animate background { duration: 180ms; } 29 - 30 - Rectangle { 31 - x: root.checked ? 18px : 2px; 32 - y: 2px; 33 - width: 18px; 34 - height: 18px; 35 - border-radius: 9px; 36 - background: white; 37 - drop-shadow-color: #00000026; 38 - drop-shadow-blur: 3px; 39 - drop-shadow-offset-y: 1px; 40 - animate x { 41 - duration: 180ms; 42 - easing: ease-out; 43 - } 44 - } 45 - } 46 - } 47 - 48 - component PaperDivider { 49 - height: 1px; 50 - Rectangle { 51 - background: Theme.line; 52 - } 53 - } 54 - 55 - component NumberField { 56 - in-out property <int> value: 0; 57 - in property <int> minimum: 0; 58 - in property <int> maximum: 100; 59 - in property <string> field-label: ""; 60 - 61 - callback edited(int); 62 - 63 - height: 28px; 64 - min-width: 80px; 65 - accessible-role: spinbox; 66 - accessible-label: root.field-label; 67 - accessible-value: "\{root.value}"; 68 - accessible-value-minimum: root.minimum; 69 - accessible-value-maximum: root.maximum; 70 - 71 - Rectangle { 72 - border-radius: 6px; 73 - background: Theme.surface; 74 - border-width: 1px; 75 - border-color: Theme.line; 76 - clip: true; 77 - 78 - HorizontalLayout { 79 - btn-minus := TouchArea { 80 - width: 26px; 81 - accessible-role: button; 82 - accessible-label: "Decrease " + root.field-label; 83 - clicked => { 84 - if root.value > root.minimum { 85 - root.value = root.value - 1; 86 - root.edited(root.value); 87 - } 88 - } 89 - 90 - Rectangle { 91 - background: btn-minus.has-hover ? Theme.tint : transparent; 92 - animate background { duration: 120ms; } 93 - } 94 - 95 - Text { 96 - text: "−"; 97 - font-size: 13px; 98 - color: root.value <= root.minimum ? Theme.ink-dis : Theme.ink-lo; 99 - horizontal-alignment: center; 100 - vertical-alignment: center; 101 - animate color { duration: 120ms; } 102 - } 103 - } 104 - 105 - Rectangle { 106 - width: 1px; 107 - background: Theme.line; 108 - } 109 - 110 - Text { 111 - text: root.value; 112 - font-size: 12px; 113 - font-weight: 500; 114 - color: Theme.ink; 115 - horizontal-alignment: center; 116 - vertical-alignment: center; 117 - horizontal-stretch: 1; 118 - } 119 - 120 - Rectangle { 121 - width: 1px; 122 - background: Theme.line; 123 - } 124 - 125 - btn-plus := TouchArea { 126 - width: 26px; 127 - accessible-role: button; 128 - accessible-label: "Increase " + root.field-label; 129 - clicked => { 130 - if root.value < root.maximum { 131 - root.value = root.value + 1; 132 - root.edited(root.value); 133 - } 134 - } 135 - 136 - Rectangle { 137 - background: btn-plus.has-hover ? Theme.tint : transparent; 138 - animate background { duration: 120ms; } 139 - } 140 - 141 - Text { 142 - text: "+"; 143 - font-size: 13px; 144 - color: root.value >= root.maximum ? Theme.ink-dis : Theme.ink-lo; 145 - horizontal-alignment: center; 146 - vertical-alignment: center; 147 - animate color { duration: 120ms; } 148 - } 149 - } 150 - } 151 - } 152 - } 153 - 154 - // A ghost button — semi-transparent surface, thin line border 155 - component PaperButton { 156 - in property <string> text; 157 - callback clicked; 158 - 159 - height: 28px; 160 - min-width: 100px; 161 - preferred-width: 160px; 162 - 163 - ta := TouchArea { 164 - clicked => { 165 - root.clicked(); 166 - } 167 - } 168 - 169 - Rectangle { 170 - border-radius: 6px; 171 - background: Theme.surface; 172 - border-width: 1px; 173 - border-color: Theme.line; 174 - 175 - Text { 176 - text: root.text; 177 - font-size: 12px; 178 - color: Theme.ink; 179 - horizontal-alignment: center; 180 - vertical-alignment: center; 181 - } 182 - } 183 - } 184 - 185 - // Primary filled button (inverted: ink bg, cream text in light; cream bg, charcoal text in dark) 186 - component PaperPrimaryButton { 187 - in property <string> text; 188 - callback clicked; 189 - 190 - height: 30px; 191 - min-width: 72px; 192 - 193 - ta := TouchArea { 194 - clicked => { 195 - root.clicked(); 196 - } 197 - } 198 - 199 - Rectangle { 200 - border-radius: 6px; 201 - background: ta.has-hover ? Theme.btn-bg-hov : Theme.btn-bg; 202 - animate background { duration: 120ms; } 203 - 204 - Text { 205 - text: root.text; 206 - font-size: 12px; 207 - font-weight: 600; 208 - color: Theme.btn-fg; 209 - horizontal-alignment: center; 210 - vertical-alignment: center; 211 - } 212 - } 213 - } 214 - 215 - // Cancel-style secondary button 216 - component PaperSecondaryButton { 217 - in property <string> text; 218 - callback clicked; 219 - 220 - height: 30px; 221 - min-width: 72px; 222 - 223 - ta := TouchArea { 224 - clicked => { 225 - root.clicked(); 226 - } 227 - } 228 - 229 - Rectangle { 230 - border-radius: 6px; 231 - background: transparent; 232 - border-width: 1px; 233 - border-color: Theme.line; 234 - 235 - Text { 236 - text: root.text; 237 - font-size: 12px; 238 - color: Theme.ink; 239 - horizontal-alignment: center; 240 - vertical-alignment: center; 241 - } 242 - } 243 - } 244 - 245 - 246 - // Styled text input 247 - component PaperInput { 248 - in-out property <string> text: ""; 249 - in property <string> placeholder-text: ""; 250 - in property <string> field-label: ""; 251 - callback edited(string); 252 - 253 - height: 32px; 254 - accessible-role: text-input; 255 - accessible-label: root.field-label; 256 - accessible-value: root.text; 257 - 258 - Rectangle { 259 - border-radius: 6px; 260 - background: ti.has-focus ? Theme.tint-soft : Theme.surface-inp; 261 - border-width: 1px; 262 - border-color: ti.has-focus ? Theme.accent-muted : Theme.line; 263 - animate background { duration: 120ms; } 264 - animate border-color { duration: 120ms; } 265 - clip: true; 266 - 267 - // Placeholder text when empty and unfocused 268 - if root.text == "" && !ti.has-focus: Text { 269 - x: 10px; 270 - text: root.placeholder-text; 271 - font-size: 13px; 272 - color: Theme.ink-dim; 273 - vertical-alignment: center; 274 - } 275 - 276 - HorizontalLayout { 277 - padding-left: 10px; 278 - padding-right: 10px; 279 - 280 - ti := TextInput { 281 - horizontal-stretch: 1; 282 - text <=> root.text; 283 - font-size: 13px; 284 - font-weight: 500; 285 - color: Theme.ink; 286 - horizontal-alignment: left; 287 - selection-background-color: Theme.accent-sel; 288 - selection-foreground-color: Theme.ink; 289 - vertical-alignment: center; 290 - edited => { 291 - root.edited(self.text); 292 - } 293 - } 294 - } 295 - } 296 - } 297 - 298 - // ── Interval card (Profile tab) ─────────────────────────────────────────────── 18 + // ── Interval card (Profile tab) ────────────────────────────────────────────── 299 19 component IntervalCard { 300 20 in property <int> index; 301 21 in property <int> work-mins; ··· 324 44 325 45 // Left column — mirrors right column row heights so index 326 46 // stays flush with the PaperInput in row 1 327 - VerticalLayout { 47 + VerticalLayout { 328 48 spacing: 8px; 329 49 width: 20px; 330 50 ··· 468 188 } 469 189 } 470 190 471 - // ── Tab button ──────────────────────────────────────────────────────────────── 472 - component TabButton { 473 - in property <string> text; 474 - in property <bool> active: false; 475 - callback clicked; 476 - 477 - height: 40px; 478 - min-width: 64px; 479 - 480 - TouchArea { 481 - clicked => { 482 - root.clicked(); 483 - } 484 - } 485 - 486 - VerticalLayout { 487 - padding-bottom: 0px; 488 - 489 - Text { 490 - text: root.text; 491 - font-size: 13px; 492 - font-weight: root.active ? 600 : 400; 493 - color: root.active ? Theme.ink : Theme.ink-mid; 494 - horizontal-alignment: left; 495 - vertical-alignment: center; 496 - } 497 - 498 - // Active indicator 499 - Rectangle { 500 - height: 2px; 501 - background: root.active ? Theme.ink : transparent; 502 - } 503 - } 504 - } 505 - 506 191 struct LevelEntry { 507 192 work-mins: int, 508 193 break-mins: int, ··· 510 195 label: string, 511 196 } 512 197 513 - // ═══════════════════════════════════════════════════════════════════════════════ 198 + // ═════════════════════════════════════════════════════════════════════════════ 514 199 // Main window 515 - // ═══════════════════════════════════════════════════════════════════════════════ 200 + // ═════════════════════════════════════════════════════════════════════════════ 516 201 export component SettingsWindow inherits Window { 517 202 title: "ioma — Settings"; 518 203 preferred-width: 760px;