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
41import { StyleMetrics } from "std-widgets.slint";
42
43struct 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
55global 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
89export 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
106component 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
151component 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
199component 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
223export 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
279component 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
287component 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
304component 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
326component 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
334export 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
364component 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}
382component 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
395component 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
403component TitleAndValueBox inherits VerticalLayout {
404 padding: 8px;
405 spacing: 8px;
406 horizontal-stretch: 100;
407}
408
409component 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
434export 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
445export 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
457component 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
475export 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
508export 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
690component 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
765export 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
806component 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
834export 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