| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: MIT |
| 3 | import { Palette } from "std-widgets.slint" ; |
| 4 | |
| 5 | struct ButtonColors { |
| 6 | base: color, |
| 7 | pressed: color, |
| 8 | hovered: color, |
| 9 | } |
| 10 | |
| 11 | struct ModeColors { |
| 12 | background: color, |
| 13 | primary: color, |
| 14 | secondary: color, |
| 15 | text-primary: color, |
| 16 | text-secondary: color, |
| 17 | destructive: color, |
| 18 | } |
| 19 | |
| 20 | export global DemoPalette { |
| 21 | in property <bool> dark-mode: Palette.color-scheme == ColorScheme.dark; |
| 22 | |
| 23 | property <ModeColors> light-mode-colors: { |
| 24 | background: #FFFFFF, |
| 25 | primary: #0E133F, |
| 26 | secondary: #FFBF63, |
| 27 | text-primary: #000, |
| 28 | text-secondary: #6284FF, |
| 29 | destructive: #FF3B30, |
| 30 | }; |
| 31 | |
| 32 | property <ModeColors> dark-mode-colors: { |
| 33 | background: #122F7B, |
| 34 | primary: #0E133F, |
| 35 | secondary: #FFBF63, |
| 36 | text-primary: #F4F6FF, |
| 37 | text-secondary: #F4F6FF, |
| 38 | destructive: #FF3B30, |
| 39 | }; |
| 40 | |
| 41 | out property <color> background: dark-mode ? dark-mode-colors.background : light-mode-colors.background; |
| 42 | out property <color> primary: dark-mode ? dark-mode-colors.primary : light-mode-colors.primary; |
| 43 | out property <color> secondary: dark-mode ? dark-mode-colors.secondary : light-mode-colors.secondary; |
| 44 | out property <color> text-primary: dark-mode ? dark-mode-colors.text-primary : light-mode-colors.text-primary; |
| 45 | out property <color> text-secondary: dark-mode ? dark-mode-colors.text-secondary : light-mode-colors.text-secondary; |
| 46 | out property <color> destructive: dark-mode ? dark-mode-colors.destructive : light-mode-colors.destructive; |
| 47 | |
| 48 | // Fixed (non-themeable) colors |
| 49 | out property <color> push-button-text-color: white; |
| 50 | |
| 51 | // Color of the home/settings/ink buttons on the left side bar |
| 52 | out property <color> active-page-icon-color: root.dark-mode ? #6284FF : #122F7B; |
| 53 | out property <color> inactive-page-icon-color: #BDC0D1; |
| 54 | out property <color> neutral-box: #BDC0D1; |
| 55 | out property <color> secondary-foreground-color: root.dark-mode ? #C1C3CA : #6C6E7A; |
| 56 | |
| 57 | // Color used for the border / outline of items that can be clicked on, such as the |
| 58 | // "Print"/"Scan" buttons, the printer queue items (for expansion) or controls such |
| 59 | // as the combo box or spin box. |
| 60 | out property <color> control-secondary: #6284FF; |
| 61 | out property <color> control-foreground: root.dark-mode ? white : #122F7B; // FIXME: the night mode color was not part of the design |
| 62 | out property <color> primary-push-button-base: #6284FF; |
| 63 | out property <ButtonColors> primary-push-button-colors: { |
| 64 | base: root.primary-push-button-base, |
| 65 | pressed: root.primary-push-button-base.darker(40%), |
| 66 | hovered: root.primary-push-button-base.darker(20%), |
| 67 | }; |
| 68 | out property <color> secondary-push-button-base: destructive; |
| 69 | out property <ButtonColors> secondary-push-button-colors: { |
| 70 | base: root.secondary-push-button-base, |
| 71 | pressed: root.secondary-push-button-base.darker(40%), |
| 72 | hovered: root.secondary-push-button-base.darker(20%), |
| 73 | }; |
| 74 | |
| 75 | out property <length> base-font-size: 16px; |
| 76 | out property <length> button-width: 130px; |
| 77 | out property <length> button-height: button-width + 30px; |
| 78 | out property <length> side-bar-width: 67px; |
| 79 | out property <length> side-bar-margin: 10px; |
| 80 | |
| 81 | } |
| 82 | |
| 83 | export component Page inherits Rectangle { |
| 84 | in property <string> header <=> h.text; |
| 85 | in property <bool> has-back-button: false; |
| 86 | |
| 87 | callback back; |
| 88 | |
| 89 | background: DemoPalette.background; |
| 90 | // Stop accidentally getting clicks dur to animation only moving page half way offscreen |
| 91 | visible: self.opacity > 0; |
| 92 | |
| 93 | TouchArea { } // protect underneath controls |
| 94 | |
| 95 | |
| 96 | |
| 97 | if (root.has-back-button) : Image { |
| 98 | x:0; |
| 99 | source: @image-url("images/back.svg" ); |
| 100 | image-fit: contain; |
| 101 | colorize: DemoPalette.control-secondary; |
| 102 | y: h.y + (h.height - self.height) / 2; |
| 103 | width: 14px; |
| 104 | height: 24px; |
| 105 | |
| 106 | TouchArea { |
| 107 | clicked => { root.back() } |
| 108 | |
| 109 | x: -14px; |
| 110 | height: 200%; |
| 111 | width: self.height; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | h := Text { |
| 116 | font-weight: 900; |
| 117 | font-size: DemoPalette.base-font-size * 2; |
| 118 | color: DemoPalette.text-primary; |
| 119 | y: 46px - self.font-size; |
| 120 | x: root.has-back-button ? 24px + 16px : 0px; |
| 121 | // Allow clicking on the title as well to get back easier when just |
| 122 | // using fingers on a small screen. |
| 123 | if (root.has-back-button) : TouchArea { |
| 124 | clicked => { root.back() } |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | export component Label inherits Text { |
| 129 | color: DemoPalette.text-primary; |
| 130 | vertical-alignment: center; |
| 131 | font-weight: 700; |
| 132 | vertical-stretch: 0; |
| 133 | } |
| 134 | |
| 135 | component SquareButton inherits Rectangle { |
| 136 | in-out property <image> img; |
| 137 | |
| 138 | callback clicked; |
| 139 | |
| 140 | border-radius: 3px; |
| 141 | border-width: 2px; |
| 142 | border-color: DemoPalette.secondary; |
| 143 | |
| 144 | touch := TouchArea { |
| 145 | clicked => { |
| 146 | root.clicked(); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | Image { |
| 151 | height: 40%; |
| 152 | width: 40%; |
| 153 | x: (parent.width - self.width)/2; |
| 154 | y: (parent.height - self.height)/2; |
| 155 | source <=> root.img; |
| 156 | image-fit: contain; |
| 157 | colorize: DemoPalette.control-secondary; |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | export component SpinBox inherits Rectangle { |
| 162 | in property <int> minimum; |
| 163 | in property <int> maximum: 100; |
| 164 | in-out property <int> value; |
| 165 | |
| 166 | height: 32px; |
| 167 | |
| 168 | HorizontalLayout { |
| 169 | spacing: 12px; |
| 170 | padding: 0; |
| 171 | |
| 172 | SquareButton { |
| 173 | clicked => { |
| 174 | if (root.value > root.minimum) { |
| 175 | root.value -= 1; |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | width: root.height - parent.padding * 2; |
| 180 | img: @image-url("images/minus.svg" ); |
| 181 | } |
| 182 | |
| 183 | Rectangle { |
| 184 | border-radius: 3px; |
| 185 | border-width: 2px; |
| 186 | border-color: DemoPalette.secondary; |
| 187 | |
| 188 | Text { |
| 189 | width: 100%; |
| 190 | height: 100%; |
| 191 | vertical-alignment: center; |
| 192 | horizontal-alignment: center; |
| 193 | text: root.value; |
| 194 | color: DemoPalette.control-foreground; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | SquareButton { |
| 199 | clicked => { |
| 200 | if (root.value < root.maximum) { |
| 201 | root.value += 1; |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | width: root.height - parent.padding * 2; |
| 206 | img: @image-url("images/plus.svg" ); |
| 207 | } |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | export component ComboBox inherits Rectangle { |
| 212 | in-out property <string> value; |
| 213 | in property <[string]> choices; |
| 214 | callback selected(int); |
| 215 | |
| 216 | border-radius: 3px; |
| 217 | border-width: 2px; |
| 218 | border-color: DemoPalette.secondary; |
| 219 | height: 32px; |
| 220 | min-width: label.x + label.width + i.width; |
| 221 | horizontal-stretch: 1; // Work around #2284 |
| 222 | |
| 223 | label := Text { |
| 224 | vertical-alignment: center; |
| 225 | horizontal-alignment: left; |
| 226 | text <=> root.value; |
| 227 | color: DemoPalette.control-foreground; |
| 228 | height: 100%; |
| 229 | x: 12px; |
| 230 | } |
| 231 | |
| 232 | i := Image { |
| 233 | source: @image-url("images/down.svg" ); |
| 234 | colorize: DemoPalette.control-secondary; |
| 235 | height: 40%; |
| 236 | width: self.height; |
| 237 | image-fit: contain; |
| 238 | x: parent.width - self.width - self.y; |
| 239 | y: (parent.height - self.height)/2; |
| 240 | } |
| 241 | |
| 242 | TouchArea { |
| 243 | clicked => { popup.show(); } |
| 244 | |
| 245 | width: 100%; |
| 246 | height: 100%; |
| 247 | } |
| 248 | |
| 249 | popup := PopupWindow { |
| 250 | x:0; |
| 251 | y: root.height; |
| 252 | width: root.width; |
| 253 | |
| 254 | Rectangle { |
| 255 | background: DemoPalette.background; |
| 256 | border-radius: 3px; |
| 257 | border-width: 2px; |
| 258 | border-color: DemoPalette.secondary; |
| 259 | } |
| 260 | |
| 261 | VerticalLayout { |
| 262 | spacing: 6px; |
| 263 | padding: 3px; |
| 264 | function textColor(hashover: bool) -> color { |
| 265 | if DemoPalette.dark-mode { |
| 266 | return DemoPalette.text-primary; |
| 267 | } |
| 268 | // if hashover { |
| 269 | return DemoPalette.text-primary; |
| 270 | // } |
| 271 | |
| 272 | } |
| 273 | |
| 274 | for value[idx] in root.choices: Rectangle { |
| 275 | border-radius: 3px; |
| 276 | background: item-area.has-hover ? DemoPalette.primary-push-button-colors.hovered : #0000; |
| 277 | |
| 278 | HorizontalLayout { |
| 279 | Text { |
| 280 | text: value; |
| 281 | color: item-area.has-hover ? DemoPalette.push-button-text-color : DemoPalette.text-primary; |
| 282 | font-size: DemoPalette.base-font-size; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | item-area := TouchArea { |
| 287 | clicked => { |
| 288 | root.value = value; |
| 289 | root.selected(idx); |
| 290 | } |
| 291 | } |
| 292 | } |
| 293 | } |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | export component CheckBox inherits Rectangle { |
| 298 | in property <string> text; |
| 299 | in-out property <bool> checked; |
| 300 | |
| 301 | height: 32px; |
| 302 | |
| 303 | HorizontalLayout { |
| 304 | spacing: 12px; |
| 305 | padding: 0; |
| 306 | SquareButton { |
| 307 | clicked => { root.checked = !root.checked; } |
| 308 | |
| 309 | width: root.height - parent.padding * 2; |
| 310 | img: root.checked ? @image-url("images/check.svg" ) : @image-url("" ); |
| 311 | } |
| 312 | |
| 313 | Text { |
| 314 | text <=> root.text; |
| 315 | vertical-alignment: center; |
| 316 | horizontal-alignment: center; |
| 317 | color: DemoPalette.control-foreground; |
| 318 | horizontal-stretch: 1; |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | TouchArea { |
| 323 | clicked => { |
| 324 | root.checked = !root.checked; |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | export component PushButton inherits Rectangle { |
| 330 | in property <string> text <=> label.text; |
| 331 | in property <bool> primary: true; |
| 332 | out property <bool> pressed: touch-area.pressed; |
| 333 | out property <ButtonColors> colors: root.primary ? DemoPalette.primary-push-button-colors : DemoPalette.secondary-push-button-colors; |
| 334 | in-out property <image> icon <=> img.source; |
| 335 | |
| 336 | callback clicked; |
| 337 | |
| 338 | border-radius: 13.5px; |
| 339 | background: root.pressed ? root.colors.pressed : (touch-area.has-hover ? root.colors.hovered : root.colors.base); |
| 340 | |
| 341 | height: 27px; // line-height in the design |
| 342 | horizontal-stretch: 1; |
| 343 | |
| 344 | HorizontalLayout { |
| 345 | padding-top: 0px; |
| 346 | padding-bottom: 0px; |
| 347 | padding-left: parent.border-radius; |
| 348 | padding-right: parent.border-radius; |
| 349 | |
| 350 | img := Image { |
| 351 | horizontal-stretch: 0; |
| 352 | colorize: DemoPalette.push-button-text-color; |
| 353 | image-fit: contain; |
| 354 | width: 17px; |
| 355 | } |
| 356 | |
| 357 | label := Text { |
| 358 | font-weight: 900; |
| 359 | font-size: DemoPalette.base-font-size * 0.975; |
| 360 | color: DemoPalette.push-button-text-color; |
| 361 | horizontal-alignment: center; |
| 362 | vertical-alignment: center; |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | touch-area := TouchArea { clicked => { root.clicked() } } |
| 367 | } |
| 368 | |