1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: MIT |
3 | |
4 | /* |
5 | The design from this file is inspired from the design in |
6 | https://github.com/peter-ha/qskinny/tree/iot-dashboard/examples/iot-dashboard |
7 | Original license: |
8 | /**************************************************************************** |
9 | ** |
10 | ** Copyright 2021 Edelhirsch Software GmbH. All rights reserved. |
11 | ** |
12 | ** Redistribution and use in source and binary forms, with or without |
13 | ** modification, are permitted provided that the following conditions are |
14 | ** met: |
15 | ** |
16 | ** * Redistributions of source code must retain the above copyright |
17 | ** notice, this list of conditions and the following disclaimer. |
18 | ** * Redistributions in binary form must reproduce the above copyright |
19 | ** notice, this list of conditions and the following disclaimer in |
20 | ** the documentation and/or other materials provided with the |
21 | ** distribution. |
22 | ** * Neither the name of the copyright holder nor the names of its |
23 | ** contributors may be used to endorse or promote products derived |
24 | ** from this software without specific prior written permission. |
25 | ** |
26 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
27 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
28 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
29 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
30 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
31 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
32 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
33 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
34 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
35 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
36 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
37 | ** |
38 | ****************************************************************************/ |
39 | */ |
40 | |
41 | import { StyleMetrics } from "std-widgets.slint" ; |
42 | |
43 | struct Palette { |
44 | menuBar : brush, |
45 | mainContent : brush, |
46 | box : brush, |
47 | lightDisplay : brush, |
48 | pieChart : brush, |
49 | roundButton : brush, |
50 | weekdayBox : brush, |
51 | text : brush, |
52 | shadow : brush, |
53 | } |
54 | |
55 | global Skin { |
56 | in property <bool> day: !StyleMetrics.dark-color-scheme; |
57 | out property <Palette> palette : root.day ? { |
58 | menuBar : #6D7BFB, |
59 | mainContent : #fbfbfb, |
60 | box : #ffffff, |
61 | lightDisplay : #ffffff, |
62 | pieChart : #ffffff, |
63 | roundButton : #f7f7f7, |
64 | weekdayBox : #f4f4f4, |
65 | text : #000, |
66 | shadow : #0001, // ### added alpha |
67 | } : { |
68 | menuBar : #2937A7, |
69 | mainContent : #040404, |
70 | box : #000000, |
71 | lightDisplay : #000000, |
72 | pieChart : #000000, |
73 | roundButton : #0a0a0a, |
74 | weekdayBox : #0c0c0c, |
75 | text : #fff, |
76 | shadow : #fff1, // ### added alpha |
77 | }; |
78 | |
79 | // From Skin::initHints in Skin.cpp |
80 | out property <length> DefaultFont: 12px; |
81 | out property <length> TinyFont: 9px; |
82 | out property <length> SmallFont: 10px; |
83 | out property <length> MediumFont: 13px; |
84 | out property <length> LargeFont: 20px; |
85 | out property <length> HugeFont: 27px; // (also, bold) |
86 | out property <length> TitleFont: 10px; // (also, bold) |
87 | } |
88 | |
89 | export component Clock inherits VerticalLayout { |
90 | in property <string> time <=> time-label.text; |
91 | |
92 | Text { |
93 | text: "Current time" ; |
94 | font-size: Skin.TitleFont; |
95 | font-weight: 700; |
96 | } |
97 | time-label := Text { |
98 | // FIXME: actual time |
99 | text: "10:02:45" ; |
100 | font-size: Skin.HugeFont; |
101 | font-weight: 700; |
102 | color: #6776FF; |
103 | } |
104 | } |
105 | |
106 | component PieChartBackground inherits Path { |
107 | in property <float> thickness; |
108 | in property <float> inner-radius; |
109 | |
110 | fill: #aaaaaa40; |
111 | |
112 | viewbox-width: 100; |
113 | viewbox-height: 100; |
114 | |
115 | MoveTo { |
116 | x: 50; |
117 | y: 0; |
118 | } |
119 | ArcTo { |
120 | radius-x: 50; |
121 | radius-y: 50; |
122 | x: 50; |
123 | y: 100; |
124 | sweep: true; |
125 | } |
126 | ArcTo { |
127 | radius-x: 50; |
128 | radius-y: 50; |
129 | x: 50; |
130 | y: 0; |
131 | sweep: true; |
132 | } |
133 | LineTo { |
134 | x: 50; |
135 | y: root.thickness; |
136 | } |
137 | ArcTo { |
138 | radius-x: root.inner-radius; |
139 | radius-y: root.inner-radius; |
140 | x: 50; |
141 | y: 100 - root.thickness; |
142 | } |
143 | ArcTo { |
144 | radius-x: root.inner-radius; |
145 | radius-y: root.inner-radius; |
146 | x: 50; |
147 | y: root.thickness; |
148 | } |
149 | } |
150 | |
151 | component PieChartFill inherits Path { |
152 | in property <float> thickness; |
153 | in property <float> inner-radius; |
154 | in property <float> progress; |
155 | in property <float> start : 0; |
156 | |
157 | viewbox-width: 100; |
158 | viewbox-height: 100; |
159 | |
160 | MoveTo { |
161 | y: 50 - 50 * cos(-root.start * 360deg); |
162 | x: 50 - 50 * sin(-root.start * 360deg); |
163 | } |
164 | |
165 | LineTo { |
166 | y: 50 - root.inner-radius * cos(-root.start * 360deg); |
167 | x: 50 - root.inner-radius * sin(-root.start * 360deg); |
168 | } |
169 | |
170 | ArcTo { |
171 | radius-x: root.inner-radius; |
172 | radius-y: root.inner-radius; |
173 | y: 50 - root.inner-radius*cos(-(root.start + root.progress) * 360deg); |
174 | x: 50 - root.inner-radius*sin(-(root.start + root.progress) * 360deg); |
175 | sweep: root.progress > 0; |
176 | large-arc: root.progress > 0.5; |
177 | } |
178 | |
179 | LineTo { |
180 | y: 50 - 50*cos(-(root.start + root.progress) * 360deg); |
181 | x: 50 - 50*sin(-(root.start + root.progress) * 360deg); |
182 | } |
183 | |
184 | ArcTo { |
185 | radius-x: 50; |
186 | radius-y: 50; |
187 | y: 50 - 50 * cos(-root.start * 360deg); |
188 | x: 50 - 50 * sin(-root.start * 360deg); |
189 | sweep: root.progress < 0; |
190 | large-arc: root.progress > 0.5; |
191 | } |
192 | |
193 | LineTo { |
194 | y: 50 - 50 * cos(-root.start * 360deg); |
195 | x: 50 - 50 * sin(-root.start * 360deg); |
196 | } |
197 | } |
198 | |
199 | component PieChartPainted inherits Rectangle { |
200 | in property <brush> brush <=> p.fill; |
201 | in property <float> progress; |
202 | in property <float> thickness: 15; |
203 | in property <float> inner-radius: 50 - root.thickness; |
204 | |
205 | back := PieChartBackground { |
206 | width: 100%; |
207 | height: 100%; |
208 | thickness: root.thickness; |
209 | inner-radius: root.inner-radius; |
210 | } |
211 | |
212 | p := PieChartFill { |
213 | width: 100%; |
214 | height: 100%; |
215 | thickness: root.thickness; |
216 | inner-radius: root.inner-radius; |
217 | progress: root.progress; |
218 | } |
219 | } |
220 | |
221 | |
222 | // From TopBar.cpp |
223 | export component TopBar inherits HorizontalLayout { |
224 | padding-left: 25px; |
225 | padding-top: 35px; |
226 | padding-right: 25px; |
227 | padding-bottom: 0px; |
228 | spacing: 0px; |
229 | |
230 | for item in [ |
231 | { string: "Living Room" , progress: 25, value: 175, color: #ff3122, gradient: @linear-gradient(0deg, #FF5C00, #FF3122) }, |
232 | { string: "Bedroom" , progress: 45, value: 205, color: #6776ff, gradient: @linear-gradient(0deg, #6776FF, #6100FF) }, |
233 | { string: "Bathroom" , progress: 15, value: 115, color: #f99055, gradient: @linear-gradient(0deg, #FFCE50, #FF3122) }, |
234 | { string: "Kitchen" , progress: 86, value: 289, color: #6776ff, gradient: @linear-gradient(0deg, #6776FF, #6100FF) }, |
235 | ] : VerticalLayout { |
236 | padding: 0px; |
237 | spacing: 0px; |
238 | |
239 | Text { |
240 | font-size: Skin.SmallFont; |
241 | text: item.string; |
242 | } |
243 | |
244 | HorizontalLayout { |
245 | PieChartPainted { |
246 | brush: item.gradient; |
247 | progress: item.progress / 100; |
248 | |
249 | Text { |
250 | width: 100%; |
251 | height: 100%; |
252 | vertical-alignment: center; |
253 | horizontal-alignment: center; |
254 | text: item.progress + "%" ; |
255 | color: item.color; |
256 | font-size: Skin.TinyFont; |
257 | } |
258 | } |
259 | |
260 | VerticalLayout { |
261 | Text { |
262 | text: item.value; |
263 | font-size: Skin.MediumFont; |
264 | } |
265 | Text { |
266 | text: "kwH" ; |
267 | font-size: Skin.SmallFont; |
268 | } |
269 | } |
270 | Rectangle {} |
271 | } |
272 | } |
273 | @children |
274 | } |
275 | |
276 | // From Box.cpp |
277 | |
278 | // This element is not in the C++ version, created to share code between Box and the Usage element |
279 | component BoxBase inherits Rectangle { |
280 | background: Skin.palette.box; |
281 | drop-shadow-offset-x: 6px; |
282 | drop-shadow-offset-y: 6px; |
283 | drop-shadow-blur: 6px; |
284 | drop-shadow-color: Skin.palette.shadow; |
285 | } |
286 | |
287 | component Box inherits BoxBase { |
288 | in property <string> title; |
289 | |
290 | VerticalLayout { |
291 | if (root.title != "" ) : Text { |
292 | text <=> root.title; |
293 | font-size: Skin.TitleFont; |
294 | font-weight: 700; |
295 | } |
296 | spacing: 10px; |
297 | padding: 15px; |
298 | |
299 | @children |
300 | } |
301 | } |
302 | |
303 | // From RoundedIcon.cpp |
304 | component RoundedIcon inherits Rectangle { |
305 | in property <bool> isBright; |
306 | in property <bool> isSmall; |
307 | in property <image> iconName <=> m-graphicLabel.source; |
308 | in property <float> background-opacity <=> background-fill.opacity; |
309 | |
310 | height: root.isSmall ? 60px : 68px; |
311 | width: root.isSmall ? 60px : 68px; |
312 | |
313 | background-fill := Rectangle { |
314 | background: root.isBright ? @linear-gradient(180deg, #ff7d34, #ff3122) : @linear-gradient(180deg, #6776FF, #6100FF); |
315 | border-radius: 6px; |
316 | opacity: 1.0; |
317 | } |
318 | |
319 | m-graphicLabel := Image { |
320 | x: (parent.width - self.width) / 2; |
321 | y: (parent.height - self.height) / 2; |
322 | } |
323 | } |
324 | |
325 | //from Usage.cpp |
326 | component UsageSpacer inherits Text { |
327 | text: "_____" ; |
328 | font-size: Skin.SmallFont; |
329 | color: #dddddd; |
330 | horizontal-stretch: 2; |
331 | } |
332 | |
333 | // Deviation: To align the items visually better, this is using a grid layout |
334 | export component Usage inherits Box { |
335 | title: "Usage" ; |
336 | horizontal-stretch: 1; |
337 | |
338 | GridLayout { |
339 | spacing: 0px; |
340 | vertical-stretch: 1; |
341 | Row { Rectangle { vertical-stretch: 0; } } |
342 | |
343 | Row { |
344 | Text { text: "Usage Today" ; font-size: Skin.SmallFont; } |
345 | UsageSpacer { } |
346 | Text { text: "0,5 kwH" ; font-size: Skin.SmallFont; } |
347 | } |
348 | |
349 | Row { |
350 | Text { text: "Usage this month" ; font-size: Skin.SmallFont; } |
351 | UsageSpacer { } |
352 | Text { text: "60 kwH" ; font-size: Skin.SmallFont; } |
353 | } |
354 | |
355 | Row { |
356 | Text { text: "Total working hours" ; font-size: Skin.SmallFont; } |
357 | UsageSpacer { } |
358 | Text { text: "125 hrs" ; font-size: Skin.SmallFont; } |
359 | } |
360 | } |
361 | } |
362 | |
363 | // From UpAndDownButton.cpp |
364 | component RoundButton inherits Image { //### QskPushButton |
365 | in property <bool> is-up; // ### QskAspect |
366 | in property <color> color: #929CB2; // Taken from the fill in the svg itself. |
367 | |
368 | callback clicked <=> ta.clicked; |
369 | |
370 | width: 30px; |
371 | |
372 | Image { |
373 | source: root.is-up ? @image-url("images/up.svg" ) : @image-url("images/down.svg" ); |
374 | x: (parent.width - self.width) / 2; |
375 | y: (parent.height - self.height) / 2; |
376 | // Deviation from qskinny: Show a darker color when pressing the button to provide feedback. |
377 | colorize: ta.pressed ? root.color.darker(80%) : root.color; |
378 | } |
379 | |
380 | ta := TouchArea { } |
381 | } |
382 | component UpAndDownButton inherits Rectangle { |
383 | callback changed(int); |
384 | // FIXME: this is actually on the RoundButton |
385 | border-radius: root.width / 2; |
386 | background: Skin.palette.roundButton; |
387 | |
388 | VerticalLayout { |
389 | u := RoundButton { is-up: true; clicked => { root.changed(+1) }} |
390 | d := RoundButton { is-up: false; clicked => { root.changed(-1) }} |
391 | } |
392 | } |
393 | |
394 | // From BoxWithButtons.cpp |
395 | component ButtonValueLabel inherits Text { |
396 | in property <string> value <=> root.text; |
397 | |
398 | font-size: Skin.HugeFont; |
399 | font-weight: 700; |
400 | color: #929cb2; |
401 | } |
402 | |
403 | component TitleAndValueBox inherits VerticalLayout { |
404 | padding: 8px; |
405 | spacing: 8px; |
406 | horizontal-stretch: 100; |
407 | } |
408 | |
409 | component BoxWithButtons inherits Box { |
410 | in property <image> iconFile <=> ri.iconName; //### in original, this is derived from title |
411 | in property <bool> isBright <=> ri.isBright; |
412 | in property <string> title- <=> titleLabel.text; |
413 | in-out property <string> value <=> val.value; |
414 | |
415 | callback changed <=> btns.changed; |
416 | |
417 | HorizontalLayout { |
418 | spacing: 20px; |
419 | |
420 | ri := RoundedIcon { } |
421 | |
422 | TitleAndValueBox { |
423 | titleLabel := Text { |
424 | font-size: Skin.TitleFont; |
425 | font-weight: 700; |
426 | } |
427 | |
428 | val := ButtonValueLabel { } |
429 | } |
430 | btns := UpAndDownButton { } |
431 | } |
432 | } |
433 | |
434 | export component IndoorTemperature inherits BoxWithButtons { |
435 | in-out property <int> temperature: 24; |
436 | |
437 | changed(delta) => { root.temperature += delta; } |
438 | |
439 | title-: "Indoor Temperature" ; |
440 | iconFile: @image-url("images/indoor-temperature.png" ); |
441 | value: (root.temperature < 0 ? "" : "+" ) + root.temperature; |
442 | isBright: true; |
443 | } |
444 | |
445 | export component Humidity inherits BoxWithButtons { |
446 | in-out property <int> humidity-percent : 30; |
447 | |
448 | changed(delta) => { root.humidity-percent += delta; } |
449 | |
450 | title-: "Humidity" ; |
451 | iconFile: @image-url("images/humidity.png" ); |
452 | value: root.humidity-percent + "%" ; |
453 | isBright: false; |
454 | } |
455 | |
456 | // from MyDevices.cpp |
457 | component Device inherits VerticalLayout { |
458 | in property <string> name <=> t.text; |
459 | in property <image> iconName <=> ri.iconName; // ### based on the name in the original |
460 | in property <bool> isBright <=> ri.isBright; |
461 | |
462 | spacing: 5px; |
463 | |
464 | ri := RoundedIcon { |
465 | background-opacity: 0.15; |
466 | isSmall: true; |
467 | } |
468 | |
469 | t := Text { |
470 | font-size: Skin.TinyFont; |
471 | horizontal-alignment: center; |
472 | } |
473 | } |
474 | |
475 | export component MyDevices inherits Box { |
476 | title: "My devices" ; |
477 | |
478 | GridLayout { |
479 | |
480 | spacing: 5px; |
481 | Row { |
482 | Device{ |
483 | name: "Lamps" ; |
484 | iconName: @image-url("images/lamps.png" ); |
485 | isBright: true; |
486 | } |
487 | Device{ |
488 | name: "Music System" ; |
489 | iconName: @image-url("images/music-system.png" ); |
490 | isBright: false; |
491 | } |
492 | } |
493 | Row { |
494 | Device{ |
495 | name: "AC" ; |
496 | iconName: @image-url("images/ac.png" ); |
497 | isBright: false; |
498 | } |
499 | Device{ |
500 | name: "Router" ; |
501 | iconName: @image-url("images/router.png" ); |
502 | isBright: true; |
503 | } |
504 | } |
505 | } |
506 | } |
507 | |
508 | export component UsageDiagram inherits Box { |
509 | // WeekDayBox |
510 | boxes := HorizontalLayout { |
511 | padding: 0px; |
512 | padding-bottom: 6px; |
513 | spacing: 6px; |
514 | |
515 | for _ in 7 : Rectangle { |
516 | background: Skin.palette.box; |
517 | drop-shadow-offset-x: 6px; |
518 | drop-shadow-offset-y: 6px; |
519 | drop-shadow-blur: 6px; |
520 | drop-shadow-color: Skin.palette.weekdayBox; |
521 | min-height: 50px; |
522 | } |
523 | |
524 | } |
525 | |
526 | Rectangle { |
527 | // ### This is somehow a hack to have another rectangle on top of the boxes |
528 | height: 0; |
529 | |
530 | VerticalLayout { |
531 | x:0; |
532 | y: -boxes.height; |
533 | height: boxes.height; |
534 | width: boxes.width; |
535 | padding: 0px; |
536 | spacing: 0px; |
537 | |
538 | HorizontalLayout { |
539 | alignment: end; |
540 | spacing: 10px; |
541 | // CaptionItem |
542 | for caption in [ |
543 | { text: "Water" , color: #6776ff, }, |
544 | { text: "Electricity" , color: #ff3122, }, |
545 | { text: "Gas" , color: #ff7d34, }, |
546 | ] : HorizontalLayout { |
547 | spacing: 10px; |
548 | padding-top: 10px; |
549 | padding-right: 20px; |
550 | |
551 | VerticalLayout { |
552 | padding: 0px; |
553 | alignment: center; |
554 | |
555 | Rectangle { |
556 | height: 8px; |
557 | width: 9px; |
558 | border-radius: 4px; |
559 | background: caption.color; |
560 | } |
561 | } |
562 | |
563 | Text { |
564 | text: caption.text; |
565 | horizontal-alignment: center; |
566 | font-size: Skin.TinyFont; |
567 | } |
568 | } |
569 | } |
570 | |
571 | Rectangle { |
572 | // The datapoint is |
573 | // FIXME: make it more curve, also fix the color |
574 | for datapoints in [ |
575 | { |
576 | values: { a: 40, b: 55, c: 60, d: 50, e: 40, f:50, g: 75, h: 80, i: 100, j: 90 }, |
577 | color: #6776ff |
578 | }, { |
579 | values: { a: 30, b: 15, c: 30, d: 40, e: 60, f: 10, g: 70, h: 20, i: 40, j: 45 }, |
580 | color: #ff3122 |
581 | } , { |
582 | values: { a: 60, b: 45, c: 60, d: 70, e: 10, f: 70, g: 20, h: 50, i: 20, j: 30 }, |
583 | color: #ff7d34, |
584 | } |
585 | ] : Path { |
586 | opacity: 0.7; |
587 | fill: @linear-gradient(180deg, datapoints.color, transparent 100%); |
588 | viewbox-width: self.width/1px; |
589 | viewbox-height: self.height/1px; |
590 | |
591 | MoveTo { |
592 | x: 0; |
593 | y: parent.viewbox-height; |
594 | } |
595 | |
596 | LineTo { |
597 | x: 0; |
598 | y: parent.viewbox-height - datapoints.values.a / 100 * parent.viewbox-height; |
599 | } |
600 | |
601 | QuadraticTo { |
602 | x: 0.5/7 * parent.viewbox-width; |
603 | y: parent.viewbox-height - datapoints.values.b / 100 * parent.viewbox-height; |
604 | control-x: 0/7 * parent.viewbox-width; |
605 | control-y: parent.viewbox-height - datapoints.values.b / 100 * parent.viewbox-height; |
606 | } |
607 | |
608 | CubicTo { |
609 | x: 1.5/7 * parent.viewbox-width; |
610 | control-1-x: 1/7 * parent.viewbox-width; |
611 | control-2-x: 1/7 * parent.viewbox-width; |
612 | y: parent.viewbox-height - datapoints.values.c / 100 * parent.viewbox-height; |
613 | control-1-y: parent.viewbox-height - datapoints.values.b / 100 * parent.viewbox-height; |
614 | control-2-y: parent.viewbox-height - datapoints.values.c / 100 * parent.viewbox-height; |
615 | } |
616 | |
617 | CubicTo { |
618 | x: 3.5/7 * parent.viewbox-width; |
619 | control-1-x: 3/7 * parent.viewbox-width; |
620 | control-2-x: 3/7 * parent.viewbox-width; |
621 | y: parent.viewbox-height - datapoints.values.e / 100 * parent.viewbox-height; |
622 | control-1-y: parent.viewbox-height - datapoints.values.d / 100 * parent.viewbox-height; |
623 | control-2-y: parent.viewbox-height - datapoints.values.e / 100 * parent.viewbox-height; |
624 | } |
625 | |
626 | CubicTo { |
627 | x: 4.5/7 * parent.viewbox-width; |
628 | control-1-x: 4/7 * parent.viewbox-width; |
629 | control-2-x: 4/7 * parent.viewbox-width; |
630 | y: parent.viewbox-height - datapoints.values.f / 100 * parent.viewbox-height; |
631 | control-1-y: parent.viewbox-height - datapoints.values.e / 100 * parent.viewbox-height; |
632 | control-2-y: parent.viewbox-height - datapoints.values.f / 100 * parent.viewbox-height; |
633 | } |
634 | |
635 | CubicTo { |
636 | x: 5.5/7 * parent.viewbox-width; |
637 | control-1-x: 5/7 * parent.viewbox-width; |
638 | control-2-x: 5/7 * parent.viewbox-width; |
639 | y: parent.viewbox-height - datapoints.values.g / 100 * parent.viewbox-height; |
640 | control-1-y: parent.viewbox-height - datapoints.values.f / 100 * parent.viewbox-height; |
641 | control-2-y: parent.viewbox-height - datapoints.values.g / 100 * parent.viewbox-height; |
642 | } |
643 | |
644 | CubicTo { |
645 | x: 6.5/7 * parent.viewbox-width; |
646 | y: parent.viewbox-height - datapoints.values.h / 100 * parent.viewbox-height; |
647 | control-1-x: 6/7 * parent.viewbox-width; |
648 | control-1-y: parent.viewbox-height - datapoints.values.g / 100 * parent.viewbox-height; |
649 | control-2-x: 6/7 * parent.viewbox-width; |
650 | control-2-y: parent.viewbox-height - datapoints.values.h / 100 * parent.viewbox-height; |
651 | } |
652 | |
653 | QuadraticTo { |
654 | x: parent.viewbox-width; |
655 | y: parent.viewbox-height - datapoints.values.i / 100 * parent.viewbox-height; |
656 | control-x: 7/7 * parent.viewbox-width; |
657 | control-y: parent.viewbox-height - datapoints.values.h / 100 * parent.viewbox-height; |
658 | } |
659 | |
660 | LineTo { |
661 | x: parent.viewbox-width; |
662 | y: parent.viewbox-height; |
663 | } |
664 | |
665 | LineTo { |
666 | x: 0; |
667 | y: parent.viewbox-height; |
668 | } |
669 | } |
670 | } |
671 | } |
672 | } |
673 | |
674 | HorizontalLayout { |
675 | padding: 0px; |
676 | padding-top: 5px; |
677 | // WeekDay |
678 | for day in ["Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" , "Sun" ] : Text { |
679 | //background: blue; |
680 | color: Skin.palette.text; |
681 | text: day; |
682 | font-size: Skin.TinyFont; |
683 | horizontal-alignment: center; |
684 | } |
685 | |
686 | } |
687 | } |
688 | |
689 | // From LightIntensity.cpp |
690 | component LightDimmer inherits Rectangle { |
691 | in property <brush> coldGradient <=> cold.fill; |
692 | in property <brush> warmGradient <=> warm.fill; |
693 | in property <float> thickness: 8; |
694 | in property <float> inner-radius: 50 - root.thickness; |
695 | out property <float> value : 50%; |
696 | out property <float> display-value : touch.pressed ? touch.new-value : root.value; |
697 | out property <angle> angle : -180deg + 180deg * root.display-value; |
698 | |
699 | back := PieChartBackground { |
700 | width: 100%; |
701 | height: 100%; |
702 | thickness: root.thickness; |
703 | inner-radius: root.inner-radius; |
704 | } |
705 | |
706 | warm := PieChartFill { |
707 | width: 100%; |
708 | height: 100%; |
709 | thickness: root.thickness; |
710 | inner-radius: root.inner-radius; |
711 | start: (root.display-value - 0.5) / 2; |
712 | progress: 0.25 - self.start; |
713 | |
714 | } |
715 | |
716 | cold := PieChartFill { |
717 | width: 100%; |
718 | height: 100%; |
719 | thickness: root.thickness; |
720 | inner-radius: root.inner-radius; |
721 | start: (root.display-value - 0.5) / 2; |
722 | progress: -0.25 - self.start; |
723 | } |
724 | |
725 | knob := Path { |
726 | width: 100%; |
727 | height: 100%; |
728 | |
729 | fill: white; |
730 | stroke-width: 1px; |
731 | stroke: #929cb2; |
732 | |
733 | viewbox-width: 100; |
734 | viewbox-height: 100; |
735 | |
736 | MoveTo { |
737 | x: 50 + (50 + root.thickness / 4) * cos(root.angle); |
738 | y: 50 + (50 + root.thickness / 4) * sin(root.angle); |
739 | } |
740 | |
741 | ArcTo { |
742 | radius-x: root.thickness / 4; |
743 | radius-y: root.thickness / 4; |
744 | x: 50 + (50 - root.thickness * 1.25) * cos(root.angle); |
745 | y: 50 + (50 - root.thickness * 1.25) * sin(root.angle); |
746 | } |
747 | |
748 | ArcTo { |
749 | radius-x: root.thickness / 4; |
750 | radius-y: root.thickness / 4; |
751 | x: 50 + (50 + root.thickness * 0.25) * cos(root.angle); |
752 | y: 50 + (50 + root.thickness * 0.25) * sin(root.angle); |
753 | } |
754 | } |
755 | |
756 | touch := TouchArea { |
757 | property <float> new-value : min(1, max(0, self.mouse-x / self.height)); |
758 | |
759 | clicked => { |
760 | root.value = self.new-value; |
761 | } |
762 | } |
763 | } |
764 | |
765 | export component LightIntensity inherits Box { |
766 | title: "Light intensity" ; |
767 | preferred-height: root.width; |
768 | |
769 | Rectangle { |
770 | vertical-stretch: 1; |
771 | |
772 | HorizontalLayout { |
773 | leftLabel := Text { |
774 | text: " 0" ; |
775 | font-size: Skin.SmallFont; |
776 | vertical-alignment: center; |
777 | } |
778 | |
779 | dimmer := LightDimmer { |
780 | warmGradient: @linear-gradient(0deg, #ff3122, #feeeb7); |
781 | coldGradient: @linear-gradient(0deg, #a7b0ff, #6776ff); |
782 | } |
783 | |
784 | rightLabel := Text { |
785 | text: "100" ; |
786 | font-size: Skin.SmallFont; |
787 | vertical-alignment: center; |
788 | } |
789 | } |
790 | |
791 | centreLabel := Text { |
792 | width: dimmer.width; |
793 | height: dimmer.height; |
794 | x: dimmer.x; |
795 | y: dimmer.y; |
796 | color: #929cb2; |
797 | text: "\{round(dimmer.display-value * 100)}%" ; |
798 | font-size: Skin.MediumFont; |
799 | vertical-alignment: center; |
800 | horizontal-alignment: center; |
801 | } |
802 | } |
803 | } |
804 | |
805 | // From MenuBar.cpp |
806 | component MenuItem inherits Rectangle { |
807 | in property <image> icon <=> i.source; |
808 | in property <string> name <=> t.text; |
809 | in-out property <bool> active; |
810 | |
811 | background: root.active ? rgba(100%, 100%, 100%, 14%) : ma.has-hover ? rgba(100%, 100%, 100%, 9%) : transparent; |
812 | |
813 | ma := TouchArea {} |
814 | |
815 | HorizontalLayout { |
816 | alignment: start; |
817 | spacing: 6px; |
818 | padding: 8px; |
819 | padding-left: 30px; |
820 | padding-right: 30px; |
821 | i := Image { |
822 | width: 14px; // Skin.cpp sets 14 pixels for MenuBarGraphicLabel::Graphic |
823 | height: self.source.height * 1px; |
824 | } |
825 | |
826 | t := Text { |
827 | color: white; |
828 | font-size: Skin.SmallFont; |
829 | } |
830 | } |
831 | } |
832 | |
833 | // From MenuBar.cpp |
834 | export component MenuBar inherits Rectangle { |
835 | out property <int> active: 0; |
836 | |
837 | background: Skin.palette.menuBar; |
838 | min-width: 140px; |
839 | |
840 | VerticalLayout { |
841 | padding-left: 0px; |
842 | padding-top: 35px; |
843 | padding-right: 0px; |
844 | padding-bottom: 12px; |
845 | spacing: 8px; |
846 | |
847 | VerticalLayout { |
848 | // Margin hint for MenuBarTopLabel::Graphic |
849 | padding-left: 50px; |
850 | padding-top: 0px; |
851 | padding-right: 50px; |
852 | padding-bottom: 54px; |
853 | |
854 | Image { |
855 | source: @image-url("images/main-icon.png" ); |
856 | height: self.source.height * 1px; |
857 | } |
858 | } |
859 | |
860 | //### In the original, the icon is derived from the name |
861 | for entry[idx] in [ |
862 | { name: "Dashboard" , icon: @image-url("images/dashboard.png" ) }, |
863 | { name: "Rooms" , icon: @image-url("images/rooms.png" ) }, |
864 | { name: "Devices" , icon: @image-url("images/devices.png" ) }, |
865 | { name: "Statistics" , icon: @image-url("images/statistics.png" ) }, |
866 | { name: "Storage" , icon: @image-url("images/storage.png" ) }, |
867 | { name: "Members" , icon: @image-url("images/members.png" ) }, |
868 | ] : MenuItem { |
869 | name: entry.name; |
870 | icon: entry.icon; |
871 | active: root.active == idx; |
872 | } |
873 | |
874 | Rectangle {} |
875 | MenuItem { name: "Logout" ; icon: @image-url("images/logout.png" ); } |
876 | } |
877 | } |
878 | |