// Respectfully stolen from https://github.com/justinledwards/ferrink-home-assistant <3 export global Settings { in-out property 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 label; in property active: false; in property 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 label; in property 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 room; in property row-index; in property selected: false; in property 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 light; in property 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 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 current-page: 0; in-out property selected-index: 0; in property selected-name: "--"; in property selected-target: "--"; in property selected-mode: "--"; in property selected-supports-heat: false; in property selected-supports-cool: false; in property selected-supports-fan: false; in property selected-fan-mode: "--"; in property outside-temperature: "--"; in property outside-high: "HIGH --"; in property outside-condition: "CONNECTING"; in property weather-kind: "cloud"; in property next-event-label: "NEXT EVENT"; in property next-event-title: "LOADING CALENDAR"; in property next-event-time: "--"; in property updated-text: "WAITING FOR HOME ASSISTANT"; in property action-status: "TAP A ROOM TO CONTROL"; in property 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; } } }