526 lines
19 KiB
Plaintext
526 lines
19 KiB
Plaintext
// Respectfully stolen from https://github.com/justinledwards/ferrink-home-assistant <3
|
||
|
||
export global Settings {
|
||
in-out property <bool> dark-mode: true;
|
||
}
|
||
|
||
export struct ThermostatData {
|
||
name: string,
|
||
current: string,
|
||
target: string,
|
||
state: string,
|
||
available: bool,
|
||
}
|
||
|
||
export struct LightData {
|
||
name: string,
|
||
on: bool,
|
||
available: bool,
|
||
}
|
||
|
||
export struct CalendarData {
|
||
date: string,
|
||
time: string,
|
||
summary: string,
|
||
calendar: string,
|
||
}
|
||
|
||
component ControlButton inherits Rectangle {
|
||
in property <string> label;
|
||
in property <bool> active: false;
|
||
in property <bool> enabled: true;
|
||
callback activated();
|
||
|
||
border-width: 2px;
|
||
border-color: Settings.dark-mode ? #777777 : black;
|
||
background: root.enabled && (touch.pressed || root.active) ? Settings.dark-mode ? #777777 : black : Settings.dark-mode ? black : white;
|
||
|
||
touch := TouchArea {
|
||
clicked => { if root.enabled { root.activated(); } }
|
||
}
|
||
|
||
Text {
|
||
text: root.label;
|
||
color: root.enabled && (touch.pressed || root.active) ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: root.label == "−" || root.label == "+" ? 48px : 16px;
|
||
font-weight: 700;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
|
||
component NavigationButton inherits Rectangle {
|
||
in property <string> label;
|
||
in property <bool> active: false;
|
||
callback activated();
|
||
|
||
border-width: 2px;
|
||
border-color: Settings.dark-mode ? #777777 : black;
|
||
background: root.active || touch.pressed ? Settings.dark-mode ? #777777 : black : Settings.dark-mode ? black : white;
|
||
|
||
touch := TouchArea {
|
||
clicked => { root.activated(); }
|
||
}
|
||
|
||
Text {
|
||
text: root.label;
|
||
color: root.active || touch.pressed ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
letter-spacing: 1px;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
|
||
component ThermostatRow inherits Rectangle {
|
||
in property <ThermostatData> room;
|
||
in property <int> row-index;
|
||
in property <bool> selected: false;
|
||
in property <string> selected-mode: "--";
|
||
|
||
callback row-activated(int);
|
||
callback change-target(int);
|
||
callback set-mode(string);
|
||
|
||
background: root.selected ? Settings.dark-mode ? #777777 : black : Settings.dark-mode ? black : white;
|
||
|
||
TouchArea {
|
||
clicked => { root.row-activated(root.row-index); }
|
||
}
|
||
|
||
Rectangle {
|
||
x: 0;
|
||
y: parent.height - 1px;
|
||
width: parent.width;
|
||
height: 1px;
|
||
background: root.selected ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
}
|
||
|
||
Text {
|
||
x: 16px;
|
||
y: 8px;
|
||
width: 330px;
|
||
height: 34px;
|
||
text: root.room.name;
|
||
color: root.selected ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 25px;
|
||
font-weight: 700;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: 17px;
|
||
y: 46px;
|
||
width: 330px;
|
||
height: 20px;
|
||
text: root.room.available ? root.room.state : "UNAVAILABLE";
|
||
color: root.selected ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: 365px;
|
||
y: 2px;
|
||
width: 135px;
|
||
height: 70px;
|
||
text: root.room.current;
|
||
color: root.selected ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 52px;
|
||
font-weight: 700;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: 530px;
|
||
y: 5px;
|
||
width: 136px;
|
||
height: 66px;
|
||
text: root.room.target;
|
||
color: root.selected ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 39px;
|
||
font-weight: 700;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
ControlButton { x: 666px - 30px; y: 0px; width: 78px; height: 78px; label: "−"; activated => { root.change-target(-1); } }
|
||
ControlButton { x: 744px - 30px; y: 0px; width: 78px; height: 78px; label: "+"; activated => { root.change-target(1); } }
|
||
|
||
ControlButton { x: 822px - 30px; y: 0px; width: 78px; height: 78px; active: root.selected-mode == "HEAT" || root.selected-mode == "HEATING"; label: "HEAT"; activated => { root.set-mode("heat"); } }
|
||
ControlButton { x: 900px - 30px; y: 0px; width: 78px; height: 78px; active: root.selected-mode == "OFF"; label: "OFF"; activated => { root.set-mode("off"); } }
|
||
}
|
||
|
||
component LightTile inherits Rectangle {
|
||
in property <LightData> light;
|
||
in property <int> light-index;
|
||
callback toggled(int);
|
||
|
||
border-width: 2px;
|
||
border-color: Settings.dark-mode ? #777777 : black;
|
||
background: root.light.on ? Settings.dark-mode ? #777777 : black : Settings.dark-mode ? black : white;
|
||
|
||
TouchArea {
|
||
clicked => { if root.light.available { root.toggled(root.light-index); } }
|
||
}
|
||
|
||
Text {
|
||
x: 14px;
|
||
y: 7px;
|
||
width: parent.width - 88px;
|
||
height: 29px;
|
||
text: root.light.name;
|
||
color: root.light.on ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
overflow: elide;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: 14px;
|
||
y: 37px;
|
||
width: parent.width - 88px;
|
||
height: 21px;
|
||
text: !root.light.available ? "UNAVAILABLE" : root.light.on ? "ON" : "OFF";
|
||
color: root.light.on ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: parent.width - 56px;
|
||
y: 8px;
|
||
width: 38px;
|
||
height: parent.height - 16px;
|
||
text: root.light.on ? "●" : "○";
|
||
color: root.light.on ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 25px;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
|
||
component CalendarRow inherits Rectangle {
|
||
in property <CalendarData> event;
|
||
|
||
background: Settings.dark-mode ? black : white;
|
||
|
||
Rectangle {
|
||
x: 0;
|
||
y: parent.height - 2px;
|
||
width: parent.width;
|
||
height: 2px;
|
||
background: Settings.dark-mode ? #777777 : black;
|
||
}
|
||
|
||
Text {
|
||
x: 0;
|
||
y: 14px;
|
||
width: 150px;
|
||
height: 27px;
|
||
text: root.event.date;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
Text {
|
||
x: 0;
|
||
y: 47px;
|
||
width: 150px;
|
||
height: 24px;
|
||
text: root.event.time;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
Text {
|
||
x: 175px;
|
||
y: 11px;
|
||
width: parent.width - 175px;
|
||
height: 39px;
|
||
text: root.event.summary;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
overflow: elide;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: 175px;
|
||
y: 55px;
|
||
width: parent.width - 175px;
|
||
height: 23px;
|
||
text: root.event.calendar;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
|
||
export component AppWindow inherits Window {
|
||
width: 1024px;
|
||
height: 600px;
|
||
background: Settings.dark-mode ? black : white;
|
||
|
||
in property <[ThermostatData]> thermostats;
|
||
in property <[LightData]> lights;
|
||
in property <[CalendarData]> calendar-events;
|
||
in-out property <int> current-page: 0;
|
||
in-out property <int> selected-index: 0;
|
||
in property <string> selected-name: "--";
|
||
in property <string> selected-target: "--";
|
||
in property <string> selected-mode: "--";
|
||
in property <bool> selected-supports-heat: false;
|
||
in property <bool> selected-supports-cool: false;
|
||
in property <bool> selected-supports-fan: false;
|
||
in property <string> selected-fan-mode: "--";
|
||
in property <string> outside-temperature: "--";
|
||
in property <string> outside-high: "HIGH --";
|
||
in property <string> outside-condition: "CONNECTING";
|
||
in property <string> weather-kind: "cloud";
|
||
in property <string> next-event-label: "NEXT EVENT";
|
||
in property <string> next-event-title: "LOADING CALENDAR";
|
||
in property <string> next-event-time: "--";
|
||
in property <string> updated-text: "WAITING FOR HOME ASSISTANT";
|
||
in property <string> action-status: "TAP A ROOM TO CONTROL";
|
||
in property <string> lights-summary: "LOADING LIGHTS";
|
||
|
||
callback select-room(int);
|
||
callback change-target(int);
|
||
callback set-mode(string);
|
||
callback set-fan-mode(string);
|
||
callback toggle-light(int);
|
||
callback refresh();
|
||
callback clean-screen();
|
||
callback user-activity();
|
||
callback enter-sleep();
|
||
callback wake-from-sleep();
|
||
callback quit();
|
||
|
||
Rectangle {
|
||
x: 0;
|
||
y: 0;
|
||
width: parent.width;
|
||
height: 5px;
|
||
background: Settings.dark-mode ? #777777 : black;
|
||
}
|
||
|
||
TouchArea {
|
||
x: 0;
|
||
y: 5px;
|
||
width: parent.width - 78px;
|
||
height: 78px;
|
||
clicked => { root.user-activity(); root.current-page = 2; }
|
||
}
|
||
|
||
Text {
|
||
x: 38px;
|
||
y: 12px;
|
||
width: parent.width - 128px;
|
||
height: 18px;
|
||
text: root.next-event-label;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
Text {
|
||
x: 38px;
|
||
y: 32px;
|
||
width: 390px;
|
||
height: 36px;
|
||
text: root.next-event-title;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
overflow: elide;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: 438px;
|
||
y: 32px;
|
||
width: 100px;
|
||
height: 36px;
|
||
text: root.next-event-time;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
horizontal-alignment: right;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Rectangle {
|
||
x: parent.width - 66px;
|
||
y: 19px;
|
||
width: 44px;
|
||
height: 44px;
|
||
border-width: 2px;
|
||
border-color: Settings.dark-mode ? #777777 : black;
|
||
background: close-touch.pressed ? Settings.dark-mode ? #777777 : black : Settings.dark-mode ? black : white;
|
||
|
||
close-touch := TouchArea {
|
||
clicked => { Settings.dark-mode = !Settings.dark-mode }
|
||
}
|
||
|
||
Text {
|
||
text: Settings.dark-mode ? "D" : "L";
|
||
color: close-touch.pressed ? Settings.dark-mode ? black : white : Settings.dark-mode ? #777777 : black;
|
||
font-size: 29px;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
|
||
if root.current-page == 0: Rectangle {
|
||
x: 0;
|
||
y: 84px;
|
||
width: root.width;
|
||
height: root.height - 84px;
|
||
background: Settings.dark-mode ? black : white;
|
||
|
||
Text {
|
||
x: 38px;
|
||
y: 10px;
|
||
width: 180px;
|
||
height: 21px;
|
||
text: "OUTSIDE";
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
Text {
|
||
x: 33px;
|
||
y: 28px;
|
||
width: 300px;
|
||
height: 116px;
|
||
text: root.outside-temperature;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 104px;
|
||
font-weight: 700;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
Text {
|
||
x: 260px;
|
||
y: 101px;
|
||
width: 150px;
|
||
height: 32px;
|
||
text: root.outside-high;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
vertical-alignment: center;
|
||
}
|
||
|
||
if root.weather-kind == "sun": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/sun.svg"); image-fit: contain; }
|
||
if root.weather-kind == "cloud-sun": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-sun.svg"); image-fit: contain; }
|
||
if root.weather-kind == "cloud-moon": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-moon.svg"); image-fit: contain; }
|
||
if root.weather-kind == "cloud": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud.svg"); image-fit: contain; }
|
||
if root.weather-kind == "cloud-rain": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-rain.svg"); image-fit: contain; }
|
||
if root.weather-kind == "cloud-rain-wind": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-rain-wind.svg"); image-fit: contain; }
|
||
if root.weather-kind == "cloud-lightning": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-lightning.svg"); image-fit: contain; }
|
||
if root.weather-kind == "snowflake": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/snowflake.svg"); image-fit: contain; }
|
||
if root.weather-kind == "cloud-fog": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-fog.svg"); image-fit: contain; }
|
||
if root.weather-kind == "moon": Image { colorize: Settings.dark-mode ? #777777 : black; x: root.width - 38px - 120px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/moon.svg"); image-fit: contain; }
|
||
|
||
Text {
|
||
x: 392px;
|
||
y: 126px;
|
||
width: parent.width - 430px;
|
||
height: 22px;
|
||
text: root.outside-condition;
|
||
color: Settings.dark-mode ? #777777 : black;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
horizontal-alignment: right;
|
||
}
|
||
|
||
Rectangle { x: 38px; y: 151px; width: parent.width - 76px; height: 3px; background: Settings.dark-mode ? #777777 : black; }
|
||
|
||
Text { x: 38px; y: 173px; width: 260px; height: 24px; text: "THERMOSTATS"; color: Settings.dark-mode ? #777777 : black; font-size: 17px; font-weight: 700; letter-spacing: 2px; }
|
||
Text { x: 403px; y: 175px; width: 135px; height: 20px; text: "NOW"; color: Settings.dark-mode ? #777777 : black; font-size: 14px; font-weight: 700; horizontal-alignment: center; }
|
||
Text { x: 568px; y: 175px; width: 136px; height: 20px; text: "SET"; color: Settings.dark-mode ? #777777 : black; font-size: 14px; font-weight: 700; horizontal-alignment: center; }
|
||
Rectangle { x: 38px; y: 205px; width: parent.width - 76px; height: 1px; background: Settings.dark-mode ? #777777 : black; }
|
||
|
||
for room[index] in root.thermostats: ThermostatRow {
|
||
x: 38px;
|
||
y: 206px + index * 80px;
|
||
width: root.width - 76px;
|
||
height: 80px;
|
||
room: room;
|
||
row-index: index;
|
||
selected: root.selected-index == index;
|
||
selected-mode <=> root.selected-mode;
|
||
row-activated(index) => { root.user-activity(); root.select-room(index); }
|
||
change-target(value) => { root.user-activity(); root.select-room(self.row-index); root.change-target(value); }
|
||
set-mode(value) => { root.user-activity(); root.select-room(self.row-index); root.set-mode(value); }
|
||
}
|
||
|
||
Text { x: root.width - 400px; y: root.height - 24px; width: 400px; vertical-alignment: bottom; horizontal-alignment: right; height: 24px; text: root.action-status; color: Settings.dark-mode ? #777777 : black; font-size: 15px; font-weight: 700; overflow: elide; }
|
||
}
|
||
|
||
if root.current-page == 1: Rectangle {
|
||
x: 0;
|
||
y: 84px;
|
||
width: root.width;
|
||
height: root.height - 84px;
|
||
background: Settings.dark-mode ? black : white;
|
||
|
||
Text { x: 38px; y: 18px; width: 230px; height: 30px; text: "LIGHTS"; color: Settings.dark-mode ? #777777 : black; font-size: 22px; font-weight: 700; letter-spacing: 2px; }
|
||
Text { x: 330px; y: 21px; width: 390px; height: 24px; text: root.lights-summary; color: Settings.dark-mode ? #777777 : black; font-size: 15px; font-weight: 700; horizontal-alignment: right; }
|
||
Rectangle { x: 38px; y: 55px; width: parent.width - 76px; height: 3px; background: Settings.dark-mode ? #777777 : black; }
|
||
|
||
for light[index] in root.lights: LightTile {
|
||
// x: index < 10 ? 38px : 390px;
|
||
// y: 72px + (index < 10 ? index : index - 10) * 74px;
|
||
x: Math.mod(index, 2) == 0 ? 38px : 390px;
|
||
y: 72px + Math.floor(index / 2) * 74px;
|
||
width: 330px;
|
||
height: 66px;
|
||
light: light;
|
||
light-index: index;
|
||
toggled(index) => { root.user-activity(); root.toggle-light(index); }
|
||
}
|
||
}
|
||
|
||
if root.current-page == 2: Rectangle {
|
||
x: 0;
|
||
y: 84px;
|
||
width: root.width;
|
||
height: root.height - 84px;
|
||
background: Settings.dark-mode ? black : white;
|
||
|
||
Text { x: 38px; y: 18px; width: 260px; height: 30px; text: "CALENDAR"; color: Settings.dark-mode ? #777777 : black; font-size: 22px; font-weight: 700; letter-spacing: 2px; }
|
||
Text { x: 400px; y: 21px; width: 320px; height: 24px; text: "NEXT 30 DAYS"; color: Settings.dark-mode ? #777777 : black; font-size: 15px; font-weight: 700; horizontal-alignment: right; }
|
||
Rectangle { x: 38px; y: 55px; width: parent.width - 76px; height: 3px; background: Settings.dark-mode ? #777777 : black; }
|
||
|
||
for event[index] in root.calendar-events: CalendarRow {
|
||
x: 38px;
|
||
y: 68px + index * 110px;
|
||
width: root.width - 76px;
|
||
height: 110px;
|
||
event: event;
|
||
}
|
||
|
||
if root.calendar-events.length == 0: Text {
|
||
x: 38px; y: 105px; width: parent.width - 76px; height: 50px; text: "NO UPCOMING EVENTS"; color: Settings.dark-mode ? #777777 : black; font-size: 24px; font-weight: 700;
|
||
}
|
||
}
|
||
|
||
NavigationButton { x: 38px; y: parent.height - 80px; width: 168px; height: 60px; label: "CLIMATE"; active: root.current-page == 0; activated => { root.user-activity(); root.current-page = 0; } }
|
||
NavigationButton { x: 222px; y: parent.height - 80px; width: 168px; height: 60px; label: "LIGHTS"; active: root.current-page == 1; activated => { root.user-activity(); root.current-page = 1; } }
|
||
NavigationButton { x: 406px; y: parent.height - 80px; width: 168px; height: 60px; label: "CALENDAR"; active: root.current-page == 2; activated => { root.user-activity(); root.current-page = 2; } }
|
||
}
|