diff --git a/wideboy/scenes/default/__init__.py b/wideboy/scenes/default/__init__.py index 840b4b2..4fa62d5 100644 --- a/wideboy/scenes/default/__init__.py +++ b/wideboy/scenes/default/__init__.py @@ -17,33 +17,8 @@ from wideboy.sprites.weather.wind import WeatherWindSprite from wideboy.sprites.image_helpers import MaterialIcons from wideboy.scenes.base import BaseScene -from wideboy.scenes.default.tiles import ( - GridTileStepsLouis, - GridTileVPN, - GridTileTransmission, - GridTileDS920Plus, - GridTileLoungeAirPM2, - GridTileSpeedtestDownload, - GridTileSpeedtestUpload, - GridTileSpeedtestPing, - GridTileBackDoor, - GridTileFrontDoor, - GridTileHouseManual, - GridTileSwitchLoungeFans, - GridTileTemperatureOutside, - GridTileTemperatureLounge, - GridTileTemperatureKitchen, - GridTileTemperatureBedroom, - GridTileElectricityCurrentDemand, - GridTileElectricityCurrentRate, - GridTileElectricityHourlyRate, - GridTileElectricityCurrentAccumulativeCost, - GridTileBatteryLevel, - GridTileBatteryDischargeRemainingTime, - GridTileBatteryChargeRemainingTime, - GridTileBatteryAcInPower, - GridTileBatteryAcOutPower, -) +from wideboy.scenes.default.tile_grid import CustomTileGrid + from wideboy.config import settings @@ -141,155 +116,11 @@ def setup(self): self.group.add(self.calendar_widget) # ===================================================================== - # HASS ENTITY ROW WIDGETS + # TILE GRID WIDGET # ===================================================================== - GRID_ENTITY_ALPHA = 230 - GRID_ENTITY_TITLE_BRIGHTNESS = 255 - GRID_ENTITY_WIDTH = 72 - GRID_ENTITY_START_X = self.width - 560 - GRID_ENTITY_MARGIN_X = 0 - - GRID_ENTITY_COLORS = [ - Color(GRID_ENTITY_TITLE_BRIGHTNESS, 0, 0, 255), - Color(GRID_ENTITY_TITLE_BRIGHTNESS, GRID_ENTITY_TITLE_BRIGHTNESS, 0, 255), - Color(0, GRID_ENTITY_TITLE_BRIGHTNESS, 0, 255), - Color(0, 0, GRID_ENTITY_TITLE_BRIGHTNESS, 255), - Color(GRID_ENTITY_TITLE_BRIGHTNESS, 0, GRID_ENTITY_TITLE_BRIGHTNESS, 255), - Color(0, GRID_ENTITY_TITLE_BRIGHTNESS, GRID_ENTITY_TITLE_BRIGHTNESS, 255), - Color( - GRID_ENTITY_TITLE_BRIGHTNESS, - GRID_ENTITY_TITLE_BRIGHTNESS, - GRID_ENTITY_TITLE_BRIGHTNESS, - 255, - ), - ] - - grid_entity_cx = GRID_ENTITY_START_X - grid_entity_color_idx = 0 - - # HOME - - hass_grid_home_entities = [ - GridTileBackDoor(), - GridTileFrontDoor(), - GridTileHouseManual(), - GridTileSwitchLoungeFans(), - ] - - self.hass_grid_home = HomeAssistantEntityGridSprite( - self, - Rect(grid_entity_cx, 0, GRID_ENTITY_WIDTH, 64), - cells=hass_grid_home_entities, - accent_color=GRID_ENTITY_COLORS[grid_entity_color_idx], - alpha=GRID_ENTITY_ALPHA, - ) - self.group.add(self.hass_grid_home) - - grid_entity_cx += GRID_ENTITY_WIDTH + GRID_ENTITY_MARGIN_X - grid_entity_color_idx += 1 - - # TEMPERATURES - - hass_grid_temp_entities = [ - GridTileTemperatureOutside(), - GridTileTemperatureLounge(), - GridTileTemperatureKitchen(), - GridTileTemperatureBedroom(), - ] - - self.hass_grid_temp = HomeAssistantEntityGridSprite( - self, - Rect(grid_entity_cx, 0, GRID_ENTITY_WIDTH, 64), - cells=hass_grid_temp_entities, - accent_color=GRID_ENTITY_COLORS[grid_entity_color_idx], - alpha=GRID_ENTITY_ALPHA, - ) - self.group.add(self.hass_grid_temp) - - grid_entity_cx += GRID_ENTITY_WIDTH + GRID_ENTITY_MARGIN_X - grid_entity_color_idx += 1 - - # SENSORS - - hass_grid_sensors_entities = [ - GridTileLoungeAirPM2(), - GridTileDS920Plus(), - ] - - self.hass_grid_sensors = HomeAssistantEntityGridSprite( - self, - Rect(grid_entity_cx, 0, GRID_ENTITY_WIDTH, 64), - cells=hass_grid_sensors_entities, - accent_color=GRID_ENTITY_COLORS[grid_entity_color_idx], - alpha=GRID_ENTITY_ALPHA, - ) - self.group.add(self.hass_grid_sensors) - - grid_entity_cx += GRID_ENTITY_WIDTH + GRID_ENTITY_MARGIN_X - grid_entity_color_idx += 1 - - # NETWORK - - hass_grid_network_entities = [ - GridTileVPN(), - # GridTileTransmission(), - GridTileSpeedtestDownload(), - GridTileSpeedtestUpload(), - GridTileSpeedtestPing(), - ] - - self.hass_grid_network = HomeAssistantEntityGridSprite( - self, - Rect(grid_entity_cx, 0, GRID_ENTITY_WIDTH, 64), - cells=hass_grid_network_entities, - accent_color=GRID_ENTITY_COLORS[grid_entity_color_idx], - alpha=GRID_ENTITY_ALPHA, - ) - self.group.add(self.hass_grid_network) - - grid_entity_cx += GRID_ENTITY_WIDTH + GRID_ENTITY_MARGIN_X - grid_entity_color_idx += 1 - - # BATTERIES - - hass_grid_battery_entities = [ - GridTileBatteryLevel(), - GridTileBatteryDischargeRemainingTime(), - GridTileBatteryChargeRemainingTime(), - GridTileBatteryAcInPower(), - GridTileBatteryAcOutPower(), - ] - - self.hass_grid_battery = HomeAssistantEntityGridSprite( - self, - Rect(grid_entity_cx, 0, GRID_ENTITY_WIDTH, 64), - cells=hass_grid_battery_entities, - accent_color=GRID_ENTITY_COLORS[grid_entity_color_idx], - alpha=GRID_ENTITY_ALPHA, - ) - self.group.add(self.hass_grid_battery) - - grid_entity_cx += GRID_ENTITY_WIDTH + GRID_ENTITY_MARGIN_X - grid_entity_color_idx += 1 - - # ELECTRICITY - - hass_grid_electricity_entities = [ - GridTileElectricityCurrentDemand(), - GridTileElectricityCurrentRate(), - GridTileElectricityHourlyRate(), - GridTileElectricityCurrentAccumulativeCost(), - ] - - self.hass_grid_electricity = HomeAssistantEntityGridSprite( - self, - Rect(grid_entity_cx, 0, GRID_ENTITY_WIDTH, 64), - cells=hass_grid_electricity_entities, - accent_color=GRID_ENTITY_COLORS[grid_entity_color_idx], - alpha=GRID_ENTITY_ALPHA, - ) - self.group.add(self.hass_grid_electricity) + self.tile_grid = CustomTileGrid(self, Rect(0, 0, 0, 0)) + self.group.add(self.tile_grid) # ===================================================================== # NOTIFICATION WIDGET @@ -321,6 +152,7 @@ def update( events: list[Event], ) -> None: super().update(clock, delta, events) + self.tile_grid.rect.topright = (self.width - 96, 0) # Handle Events diff --git a/wideboy/scenes/default/tile_grid.py b/wideboy/scenes/default/tile_grid.py new file mode 100644 index 0000000..3a69c09 --- /dev/null +++ b/wideboy/scenes/default/tile_grid.py @@ -0,0 +1,303 @@ +import datetime +import enum +import pygame +import random + +from wideboy.sprites.tile_grid import ( + TileGrid, + HorizontalCollapseTileGridColumn, + VerticalCollapseTileGridCell, + CommonColors, + FontAwesomeIcons, +) + + +# CUSTOM SUBCLASSES + + +class GridCell(VerticalCollapseTileGridCell): + cell_color_background = CommonColors.COLOR_GREY_DARK + icon_color_background = CommonColors.COLOR_GREY + + +# CUSTOM FUNCTIONS + + +def format_watts(watts: int): + return "{:.0f}W".format(watts) if watts < 1000 else "{:.1f}kW".format(watts / 1000) + + +# TILE DEFINITIONS + +# Electricity Tiles + + +class CellElectricityDemand(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_BOLT + + @property + def value(self): + return float( + self.state.get("sensor.octopus_energy_electricity_current_demand", 0) + ) + + @property + def open(self): + return self.value > 500 + + @property + def label(self): + return format_watts(self.value) + + @property + def cell_color_background(self): + return ( + CommonColors.COLOR_RED_DARK + if self.value > 1000 + else CommonColors.COLOR_GREY_DARK + ) + + @property + def icon_color_background(self): + return CommonColors.COLOR_RED if self.value > 1000 else CommonColors.COLOR_GREY + + +class CellElectricityRate(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_HOURGLASS + + @property + def value(self): + return float( + self.state.get("sensor.octopus_energy_electricity_current_rate", 0) + ) + + @property + def label(self): + return f"£{self.value:.2f}" + + +class CellElectricityAccumulativeCost(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_CLOCK + + @property + def value(self): + return float( + self.state.get( + "sensor.octopus_energy_electricity_current_accumulative_cost", 0 + ) + ) + + @property + def label(self): + return f"£{self.value:.2f}" + + +# Battery Tiles + + +class CellBatteryLevel(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_BATTERY_FULL + + @property + def value(self): + return int(self.state.get("sensor.delta_2_max_downstairs_battery_level", 0)) + + @property + def label(self): + return f"{self.value}%" + + +class CellBatteryACInput(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_PLUG_CIRCLE_PLUS + + @property + def value(self): + return int(self.state.get("sensor.delta_2_max_downstairs_ac_in_power", 0)) + + @property + def open(self): + return self.value > 100 + + @property + def label(self): + return format_watts(self.value) + + +class CellBatteryACOutput(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_PLUG_CIRCLE_MINUS + + @property + def value(self): + return int(self.state.get("sensor.delta_2_max_downstairs_ac_out_power", 0)) + + @property + def open(self): + return self.value > 100 + + @property + def label(self): + return format_watts(self.value) + + +# Network Tiles + + +class CellSpeedTestDownload(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_CIRCLE_ARROW_DOWN + + @property + def value(self): + return int(self.state.get("sensor.speedtest_download_average", 0)) + + @property + def open(self): + return self.value > 500 + + @property + def label(self): + return f"{self.value}Mb" + + +class CellSpeedTestUpload(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_CIRCLE_ARROW_UP + + @property + def value(self): + return int(self.state.get("sensor.speedtest_upload_average", 0)) + + @property + def open(self): + return self.value > 500 + + @property + def label(self): + return f"{self.value}Mb" + + +class CellSpeedTestPing(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_HEART_PULSE + + @property + def value(self): + return int(self.state.get("sensor.speedtest_ping_average", 0)) + + @property + def open(self): + return self.value > 25 + + @property + def label(self): + return f"{self.value}ms" + + +class CellDS920VolumeUsage(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_HARD_DRIVE + + @property + def value(self): + return int(self.state.get("sensor.ds920plus_volume_used", 0)) + + @property + def open(self): + return self.value > 60 + + @property + def label(self): + return f"{self.value}%" + + +# Test Tiles + + +class CellTestRandom(GridCell): + icon_codepoint = FontAwesomeIcons.ICON_FA_DICE_THREE + + @property + def value(self): + return datetime.datetime.now().second + + @property + def open(self): + return 0 <= self.value % 5 <= 2 + + @property + def label(self): + return f"{self.value}" + + +# Switch Tiles + + +class CellSwitchLoungeFan(GridCell): + label = "Fan" + icon_codepoint = FontAwesomeIcons.ICON_FA_FAN + + @property + def value(self): + return self.state.get("fan", "off") + + @property + def open(self): + return self.value == "on" + + +# CUSTOM COLUMNS + +rainbox_colors = [ + CommonColors.COLOR_RED, + CommonColors.COLOR_ORANGE, + CommonColors.COLOR_YELLOW, + CommonColors.COLOR_GREEN, + CommonColors.COLOR_BLUE, + CommonColors.COLOR_PURPLE, + CommonColors.COLOR_PINK, +] + + +class GridColumnHomeLab(HorizontalCollapseTileGridColumn): + border_width = 1 + border_color = rainbox_colors[0] + cells = [ + CellDS920VolumeUsage, + CellSpeedTestDownload, + CellSpeedTestUpload, + CellSpeedTestPing, + ] + + +class GridColumnBattery(HorizontalCollapseTileGridColumn): + border_width = 1 + border_color = rainbox_colors[1] + cells = [ + CellBatteryLevel, + CellBatteryACInput, + CellBatteryACOutput, + ] + + +class GridColumnElectricity(HorizontalCollapseTileGridColumn): + border_width = 1 + border_color = rainbox_colors[2] + cells = [ + CellElectricityDemand, + CellElectricityRate, + CellElectricityAccumulativeCost, + ] + + +class GridColumnTest(HorizontalCollapseTileGridColumn): + border_width = 1 + border_color = rainbox_colors[3] + cells = [CellTestRandom] + + +# CUSTOM GRID + + +class CustomTileGrid(TileGrid): + columns = [ + GridColumnHomeLab, + # GridColumnTest, + GridColumnBattery, + GridColumnElectricity, + ] diff --git a/wideboy/scenes/default/tiles.py b/wideboy/scenes/default/tiles.py deleted file mode 100644 index c27897a..0000000 --- a/wideboy/scenes/default/tiles.py +++ /dev/null @@ -1,316 +0,0 @@ -from pygame import Color -from wideboy.sprites.image_helpers import MaterialIcons, number_to_color -from wideboy.sprites.homeassistant.entity_grid import HomeAssistantEntityGridTile -from wideboy.sprites.homeassistant.entity_row import HomeAssistantEntityTile -from wideboy.config import settings - -from typing import Any, Optional, List - - -# COLORS - -COLORS_DIM_BRIGHTNESS = 128 -COLORS_DIM_ALPHA = 255 -COLORS_TRAFFIC_LIGHT_DIM = [ - Color(COLORS_DIM_BRIGHTNESS, 0, 0, COLORS_DIM_ALPHA), - Color(COLORS_DIM_BRIGHTNESS, COLORS_DIM_BRIGHTNESS, 0, COLORS_DIM_ALPHA), - Color(0, COLORS_DIM_BRIGHTNESS, 0, COLORS_DIM_ALPHA), -] - -# GENERAL - - -class GridTileStepsLouis(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_DIRECTIONS_WALK - steps_per_day = 6000 - - def process(self, state): - value = float(state.get("sensor.steps_louis", 0)) - self.visible = value > 0 - self.label = f"{value:.0f}" - self.label_color_bg = number_to_color( - value / self.steps_per_day, colors=COLORS_TRAFFIC_LIGHT_DIM - ) - self.progress = value / self.steps_per_day - - -# NETWORK - - -class GridTileVPN(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_LOCK - - def process(self, state): - value = state.get("sensor.privacy_ip_info", None) - self.visible = value == settings.secrets.home_ip - self.label = f"VPN DOWN ({value})" - - -class GridTileTransmission(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_VPN_LOCK - - def process(self, state): - value = state.get("sensor.transmission_down_speed", 0) - self.visible = value > 0 - self.label = f"{value:.0f}Mbps" - - -class GridTileDS920Plus(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_DNS - - def process(self, state): - value = float(state.get("sensor.ds920plus_volume_used", 0)) - self.visible = value > 66.66 - self.label = f"{value:.0f}%" - self.label_color_bg = number_to_color( - value / 100, colors=COLORS_TRAFFIC_LIGHT_DIM - ) - self.progress = value / 100 - - -class GridTileSpeedtestDownload(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_DOWNLOAD - - def process(self, state): - self.visible = True - value = float(state.get("sensor.speedtest_download_average", 0)) - self.label = f"{value:.0f}M" - self.label_color_bg = number_to_color( - value / 900, colors=COLORS_TRAFFIC_LIGHT_DIM - ) - self.progress = value / 900 - - -class GridTileSpeedtestUpload(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_UPLOAD - - def process(self, state): - self.visible = True - value = float(state.get("sensor.speedtest_upload_average", 0)) - self.label = f"{value:.0f}M" - self.label_color_bg = number_to_color( - value / 900, colors=COLORS_TRAFFIC_LIGHT_DIM - ) - self.progress = value / 900 - - -class GridTileSpeedtestPing(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_WIFI - - def process(self, state): - self.visible = True - value = state.get("sensor.speedtest_ping_average", 0) - self.label = f"{value:.0f}ms" - self.label_color_bg = number_to_color( - value, [10, 20, 30], colors=COLORS_TRAFFIC_LIGHT_DIM, invert=True - ) - self.progress = value / 30 - - -# SENSORS - - -class GridTileLoungeAirPM2(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_AC_UNIT - - def process(self, state): - value = int(state.get("sensor.core_300s_pm2_5", 0)) - self.visible = value > 0 - self.label = f"{value}" - - -class GridTileBinCollection(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_DELETE - - def process(self, state): - value = state.get("calendar.bin_collection") - self.visible = value <= 1 - self.label = f"{value}" - - -class GridTileBackDoor(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_DOOR - label = "Back" - - def process(self, state): - self.visible = ( - state.get("binary_sensor.back_door_contact_sensor_contact") == True - ) - - -class GridTileFrontDoor(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_DOOR - label = "Front" - - def process(self, state): - self.visible = ( - state.get("binary_sensor.front_door_contact_sensor_contact") == True - ) - - -class GridTileTemperatureOutside(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_HOME - - def process(self, state): - self.visible = True - value = float(state.get("sensor.blink_back_temperature", 0)) - self.label = f"{value:.0f}°" - - -class GridTileTemperatureLounge(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_SOFA - - def process(self, state): - self.visible = True - value = float(state.get("sensor.hue_motion_sensor_1_temperature", 0)) - self.label = f"{value:.0f}°" - - -class GridTileTemperatureKitchen(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_KITCHEN - - def process(self, state): - self.visible = True - value = float(state.get("sensor.kitchen_temperature_sensor_temperature", 0)) - self.label = f"{value:.0f}°" - - -class GridTileTemperatureBedroom(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_BED - - def process(self, state): - self.visible = True - value = float(state.get("sensor.bedroom_temperature_sensor_temperature", 0)) - self.label = f"{value:.0f}°" - - -# SWITCHES - - -class GridTileHouseManual(HomeAssistantEntityGridTile): - icon = "M" - label = "ON" - - def process(self, state): - self.visible = state.get("input_boolean.house_manual") == True - - -class GridTileSwitchLoungeFans(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_AC_UNIT - label = "ON" - - def process(self, state): - self.visible = state.get("switch.lounge_fans") == True - - -# BATTERY - - -class GridTileBatteryLevel(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_BATTERY - - def process(self, state): - value = float(state.get("sensor.delta_2_max_downstairs_battery_level", 0)) - self.visible = value > 0 - self.label = f"{value:.0f}%" - self.label_color_bg = number_to_color( - value / 100, colors=COLORS_TRAFFIC_LIGHT_DIM - ) - self.progress = value / 100 - - -class GridTileBatteryDischargeRemainingTime(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_HOURGLASS - icon_color_fg = Color(255, 0, 0, 255) - - def process(self, state): - value = float( - state.get("sensor.delta_2_max_downstairs_discharge_remaining_time", 0) - ) - hours, mins = value // 60, value % 60 - self.visible = value > 0 - self.label = f"{hours:.0f}h{mins:.0f}m" - - -class GridTileBatteryChargeRemainingTime(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_HOURGLASS - icon_color_fg = Color(0, 255, 0, 255) - - def process(self, state): - value = float( - state.get("sensor.delta_2_max_downstairs_charge_remaining_time", 0) - ) - hours, mins = value // 60, value % 60 - self.visible = value > 0 - self.label = f"{hours:.0f}h{mins:.0f}m" - - -class GridTileBatteryAcInPower(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_POWER - icon_color_fg = Color(0, 255, 0, 255) - - def process(self, state): - value = float(state.get("sensor.delta_2_max_downstairs_ac_in_power", 0)) - self.visible = value > 0 - self.label = f"{value:.0f}w" - - -class GridTileBatteryAcOutPower(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_POWER - icon_color_fg = Color(255, 0, 0, 255) - - def process(self, state): - value = float(state.get("sensor.delta_2_max_downstairs_ac_out_power", 0)) - self.visible = value > 0 - self.label = f"{value:.0f}w" - - -# ELECTRICITY - - -class GridTileElectricityCurrentDemand(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_BOLT - icon_color_fg = Color(192, 192, 192, 255) - - def process(self, state): - value = float(state.get("sensor.octopus_energy_electricity_current_demand", 0)) - self.visible = value > 0 - self.label = f"{value:.0f}w" - self.label_color_bg = number_to_color( - value, [300, 600, 900], colors=COLORS_TRAFFIC_LIGHT_DIM, invert=True - ) - - -class GridTileElectricityCurrentRate(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_SYMBOL_AT - - def process(self, state: dict[str, Any]): - value = float(state.get("sensor.octopus_energy_electricity_current_rate", 0)) - self.visible = value > 0 - self.label = f"£{value:.2f}" - self.label_color_bg = number_to_color( - value, - ranges=[0.10, 0.30, 1.0], - colors=COLORS_TRAFFIC_LIGHT_DIM, - invert=True, - ) - - -class GridTileElectricityHourlyRate(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_CURRENCY_DOLLAR - - def process(self, state): - value = float(state.get("sensor.electricity_hourly_rate", 0)) - self.visible = value > 0 - self.label = f"£{value:.2f}" - - -class GridTileElectricityCurrentAccumulativeCost(HomeAssistantEntityGridTile): - icon = MaterialIcons.MDI_SCHEDULE - - def process(self, state): - value = float( - state.get("sensor.octopus_energy_electricity_current_accumulative_cost", 0) - ) - self.visible = value > 0 - self.label = f"£{value:.2f}" diff --git a/wideboy/testing/sprites/lib/tile_grid.py b/wideboy/sprites/tile_grid/__init__.py similarity index 88% rename from wideboy/testing/sprites/lib/tile_grid.py rename to wideboy/sprites/tile_grid/__init__.py index ee20fcb..c2007c0 100644 --- a/wideboy/testing/sprites/lib/tile_grid.py +++ b/wideboy/sprites/tile_grid/__init__.py @@ -6,7 +6,9 @@ from datetime import datetime from typing import Any, Callable, List, Dict, Optional, Tuple, Type, TypeVar, cast -from .helpers import ( +from wideboy.scenes.base import BaseScene +from wideboy.sprites.base import BaseSprite +from wideboy.sprites.tile_grid.helpers import ( Animator, AnimatorState, FontAwesomeIcons, @@ -24,30 +26,20 @@ # CONSTANTS -TILE_GRID_CELL_WIDTH = 64 +TILE_GRID_CELL_WIDTH = 96 TILE_GRID_CELL_HEIGHT = 12 TILE_GRID_CELL_ICON_WIDTH = 15 TILE_GRID_CELL_ICON_HEIGHT = 12 # TILE GRID -# Base Classes - - -class BaseGroup(pygame.sprite.Group): - pass - - -class BaseSprite(pygame.sprite.Sprite): - pass - # Mixins class StyleMixin: # Cell - cell_color_background: pygame.Color = pygame.Color(16, 16, 16, 255) + cell_color_background: pygame.Color = pygame.Color(16, 16, 16, 64) # Label label_antialias: bool = True label_outline: bool = True @@ -65,7 +57,7 @@ class StyleMixin: # Tile Grid Cell Sprites -class TileGridCell(BaseSprite, StyleMixin): +class TileGridCell(pygame.sprite.Sprite, StyleMixin): style: Dict width: int = TILE_GRID_CELL_WIDTH height: int = TILE_GRID_CELL_HEIGHT @@ -73,7 +65,7 @@ class TileGridCell(BaseSprite, StyleMixin): label: str = "" def __init__(self, state): - super().__init__(state) + super().__init__() self.state = state self.image = pygame.Surface((self.width, self.height)) self.image.fill(self.cell_color_background) @@ -114,7 +106,7 @@ def __repr__(self): # Tile Grid Column Sprites -class TileGridColumn(BaseSprite): +class TileGridColumn(pygame.sprite.Sprite): width: int = TILE_GRID_CELL_WIDTH height: int = 0 border_width: int = 0 @@ -130,7 +122,8 @@ def __init__(self, state): ( self.width + self.border_padding, self.height, - ) + ), + pygame.SRCALPHA, ) self.rect = self.image.get_rect() @@ -141,7 +134,7 @@ def render(self): ch = 0 mh = sum([cell.image.get_height() for cell in self.cells_inst]) self.image = pygame.Surface((self.rect.width, mh)) - self.image.fill(pygame.Color(0, 0, 0)) + self.image.fill(pygame.Color(0, 0, 0, 0)) for cell in self.cells_inst: cell.update() self.image.blit(cell.image, (self.border_padding, ch)) @@ -159,26 +152,28 @@ class TileGrid(BaseSprite): columns: List[Any] state: Dict = dict() - def __init__(self, state, x=0, y=0): - super().__init__() - self.state = state + def __init__(self, scene: BaseScene, rect: pygame.Rect): + super().__init__(scene, rect) + self.state = self.scene.engine.hass.state self.columns_inst = [column(self.state) for column in self.columns] - self.image = pygame.Surface((0, 0)) + self.image = pygame.Surface((0, 0), pygame.SRCALPHA) + self.image.fill(pygame.Color(0, 0, 0, 0)) self.rect = self.image.get_rect() def __repr__(self): return f"TileGrid(columns={self.groups})" - def update(self): + def update(self, frame, clock, delta, events): + self.dirty = 1 # TODO: Optimise self.render() def render(self): cw = 0 mw = sum([column.image.get_width() for column in self.columns_inst]) mh = max([column.image.get_width() for column in self.columns_inst]) - self.image = pygame.Surface((mw, mh)) + self.image = pygame.Surface((mw, mh), pygame.SRCALPHA) self.rect.height = mh - self.image.fill((0, 0, 0, 0)) + self.image.fill(pygame.Color(0, 0, 0, 0)) for column in self.columns_inst: column.update() self.image.blit(column.image, (cw, 0)) diff --git a/wideboy/testing/sprites/lib/helpers.py b/wideboy/sprites/tile_grid/helpers.py similarity index 99% rename from wideboy/testing/sprites/lib/helpers.py rename to wideboy/sprites/tile_grid/helpers.py index 5a70223..2aaf9a5 100644 --- a/wideboy/testing/sprites/lib/helpers.py +++ b/wideboy/sprites/tile_grid/helpers.py @@ -9,7 +9,7 @@ ICON_FONT_SIZE = 9 LABEL_FONT_FILENAME = "fonts/bitstream-vera.ttf" -LABEL_FONT_SIZE = 10 +LABEL_FONT_SIZE = 11 # CUSTOM COLORS diff --git a/wideboy/testing/sprites/__init__.py b/wideboy/testing/sprites/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/wideboy/testing/sprites/archive/rawgraphics.py b/wideboy/testing/sprites/archive/rawgraphics.py deleted file mode 100644 index c1a201a..0000000 --- a/wideboy/testing/sprites/archive/rawgraphics.py +++ /dev/null @@ -1,340 +0,0 @@ -import enum -import logging -import pygame -import random -import time -from typing import List, Tuple - -from .utils import render_text - - -logger = logging.getLogger(__name__) -logger.addHandler(logging.StreamHandler()) -logger.setLevel(logging.DEBUG) - -SCREEN_WIDTH = 256 -SCREEN_HEIGHT = 64 - -# Sprites - -FONT_FILENAME = "fonts/bitstream-vera.ttf" -FONT_SIZE = 12 - - -class CellStates(enum.Enum): - OPEN = enum.auto() - CLOSED = enum.auto() - OPENING = enum.auto() - CLOSING = enum.auto() - - -class CellCollapseStyle(enum.Enum): - HORIZONTAL = enum.auto() - VERTICAL = enum.auto() - - -class BaseSprite(pygame.sprite.Sprite): - pass - - -class CellGroup(pygame.sprite.Group): - def update(self): - super().update() - cy = 0 - for sprite in self.sprites(): - sprite.rect.y = cy - cy += sprite.rect.height - - -class CellRow(BaseSprite): - def __init__(self, x: int, y: int, width: int, height: int, cells: [BaseSprite]): - super().__init__() - self.x = x - self.y = y - self.width = width - self.height = height - self.cells = cells - self.image = pygame.Surface((self.width, self.height)) - self.rect = self.image.get_rect() - self.render() - - def update(self): - self.render() - - def render(self): - surface = pygame.Surface((self.width, self.height)) - cx = 0 - for cell in self.cells: - cell.update() - cell.render() - surface.blit(cell.image, (cx, 0)) - cx += cell.rect.width - print(([cell.evaluate() for cell in self.cells])) - self.image = pygame.Surface((cx, self.height)) - self.image.blit(surface, (0, 0)) - self.rect = self.image.get_rect() - - -class CellColumn(BaseSprite): - def __init__( - self, - x: int, - y: int, - width: int, - height: int, - cells: [BaseSprite], - border_width: int = 1, - border_color: pygame.Color = pygame.Color(255, 255, 255), - border_padding: int = 0, - ): - super().__init__() - self.x = x - self.y = y - self.width = width - self.height = height - self.cells = cells - self.border_width = border_width - self.border_color = border_color - self.border_padding = border_padding - self.image = pygame.Surface((self.width, self.height)) - self.rect = self.image.get_rect() - self.render() - - def update(self): - self.render() - - def render(self): - surface = pygame.Surface((self.width, self.height)) - surface.fill( - self.border_color, pygame.Rect(0, 0, self.border_width, self.height) - ) - cx, cy = self.border_width + self.border_padding, 0 - for cell in self.cells: - cell.update() - cell.render() - surface.blit(cell.image, (cx, cy)) - cy += cell.rect.height if cell.rect.height > 1 else 0 - self.image.blit(surface, (0, 0)) - self.rect = self.image.get_rect() - - def evaluate(self): - test = lambda cell: cell.open_state == CellStates.OPEN - logger.debug(f"CellColumn.evaluate: {[test(cell) for cell in self.cells]}") - return any([test(cell) for cell in self.cells]) - - -class Cell(BaseSprite): - def __init__( - self, - width: int, - height: int, - text: str, - font: str = FONT_FILENAME, - size: int = FONT_SIZE, - color_fg=pygame.Color(255, 255, 255), - color_bg=pygame.Color(0, 0, 0, 0), - color_outline=None, - padding: Tuple[int, int] = (0, -2), - evaluate_cb=lambda state: True, - ): - super().__init__() - self.width = width - self.height = height - self.text = text - self.font = font - self.size = size - self.color_fg = color_fg - self.color_bg = color_bg - self.color_outline = color_outline - self.padding = padding - self.evaluate_cb = evaluate_cb - self.render() - - def render(self): - self.image = pygame.Surface( - (self.width, self.height), - ) - self.image.fill(self.color_bg) - text_surface = render_text( - self.text, - font_filename=self.font, - font_size=self.size, - color_fg=self.color_fg, - color_outline=self.color_outline, - ) - self.image.blit(text_surface, (3 + self.padding[0], 0 + self.padding[1])) - - def evaluate(self): - state = dict() - return self.evaluate_cb(state) - - -class Collapsible(BaseSprite): - def __init__( - self, - x: int, - y: int, - width: int, - height: int, - sprite: BaseSprite, - width_closed: int = 0, - height_closed: int = 0, - open: bool = True, - collapse_style: CellCollapseStyle = CellCollapseStyle.HORIZONTAL, - speed: int = 1, - color_bg: pygame.Color = pygame.Color(0, 0, 0, 0), - debug: bool = False, - ): - super().__init__() - self.x = x - self.y = y - self.width_open = width - self.width_closed = width_closed - self.width = self.width_open if open else self.width_closed - self.height_open = height - self.height_closed = height_closed - self.height = self.height_open if open else self.height_closed - self.sprite = sprite - self.open_state = CellStates.OPEN if open else CellStates.CLOSED - self.collapse_style = collapse_style - self.speed = speed - self.color_bg = color_bg - self.debug = debug - self.image = pygame.Surface((self.width, self.height)) - self.rect = self.image.get_rect() - - def update(self): - if self.debug: - logger.debug(f"Cell.update: {self.open_state}") - if self.open_state in [CellStates.OPEN, CellStates.CLOSED]: - self.open_state = ( - CellStates.OPENING if self.evaluate() else CellStates.CLOSING - ) - self.handle_animation() - self.render() - - def handle_animation(self): - if self.open_state == CellStates.OPENING: - # Open Vertical - if self.collapse_style == CellCollapseStyle.VERTICAL: - self.height += self.speed - if self.height >= self.height_open: - self.height = self.height_open - if self.height == self.height_open: - self.open_state = CellStates.OPEN - # Open Horizontal - if self.collapse_style == CellCollapseStyle.HORIZONTAL: - self.width += self.speed - if self.width >= self.width_open: - self.width = self.width_open - if self.width == self.width_open: - self.open_state = CellStates.OPEN - elif self.open_state == CellStates.CLOSING: - # Close Vertical - if self.collapse_style == CellCollapseStyle.VERTICAL: - self.height -= self.speed - if self.height <= self.height_closed: - self.height = self.height_closed - if self.height == self.height_closed: - self.open_state = CellStates.CLOSED - # Close Horizontal - if self.collapse_style == CellCollapseStyle.HORIZONTAL: - self.width -= self.speed - if self.width <= self.width_closed: - self.width = self.width_closed - if self.width == self.width_closed: - self.open_state = CellStates.CLOSED - - def render(self, force_redraw: bool = False): - if force_redraw or self.open_state in [CellStates.OPENING, CellStates.CLOSING]: - self.image = pygame.Surface((self.width, self.height)) - self.image.fill(self.color_bg) - self.rect = self.image.get_rect() - if self.sprite.image is not None and self.image is not None: - self.sprite.update() - self.image.blit(self.sprite.image, (0, 0)) - self.rect = self.image.get_rect() - - def toggle(self): - if self.open_state == CellStates.OPEN or self.open_state == CellStates.OPENING: - self.open_state = CellStates.CLOSING - elif ( - self.open_state == CellStates.CLOSED - or self.open_state == CellStates.CLOSING - ): - self.open_state = CellStates.OPENING - if self.debug: - logger.debug(f"Cell.toggle: {self.open_state}") - - def evaluate(self): - return self.sprite.evaluate() - - -# Main Logic - -pygame.init() -screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SCALED) - -clock = pygame.time.Clock() -FPS = 50 -DEBUG = True - -sprite_group: pygame.sprite.Group = pygame.sprite.Group() - -CELL_WIDTH = 64 -CELL_HEIGHT = 12 - -tile_speedtest_download = Cell( - CELL_WIDTH, CELL_HEIGHT, "Download", evaluate_cb=lambda v: True -) - -column_tiles_internet = [tile_speedtest_download] -columns = [column_tiles_internet] - -cell_columns = [] -for column in columns: - column_cells = [] - for column_tile in column: - collapsible = Collapsible( - 0, - 0, - CELL_WIDTH, - CELL_HEIGHT, - column_tile, - collapse_style=CellCollapseStyle.VERTICAL, - debug=DEBUG, - ) - column_cells.append(collapsible) - cell_column = CellColumn(0, 0, CELL_WIDTH, SCREEN_HEIGHT, column_cells) - cell_column_collapsible = Collapsible( - 0, - 0, - CELL_WIDTH, - CELL_HEIGHT * len(column_cells), - cell_column, - collapse_style=CellCollapseStyle.HORIZONTAL, - debug=DEBUG, - ) - cell_columns.append(cell_column_collapsible) - -row = CellRow(0, 0, CELL_WIDTH * len(cell_columns), SCREEN_HEIGHT, cell_columns) - -sprite_group.add(row) - - -running = True -while running: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - running = False - # if event.type == pygame.KEYDOWN: - # if pygame.K_1 <= event.key <= pygame.K_8: - # numkey = event.key - pygame.K_0 - 1 - # sprites[numkey].toggle() - - screen.fill((0, 0, 0, 255)) - sprite_group.update() - sprite_group.draw(screen) - pygame.display.flip() - clock.tick(FPS) -pygame.quit() diff --git a/wideboy/testing/sprites/__main__.py b/wideboy/testing/tile_grid/__main__.py similarity index 92% rename from wideboy/testing/sprites/__main__.py rename to wideboy/testing/tile_grid/__main__.py index 4d91866..d8933b2 100644 --- a/wideboy/testing/sprites/__main__.py +++ b/wideboy/testing/tile_grid/__main__.py @@ -6,6 +6,7 @@ import time from typing import Dict, List, Tuple +from wideboy.scenes.base import BaseScene from .tiles import CustomTileGrid logger = logging.getLogger(__name__) @@ -101,9 +102,17 @@ def randomise_state_rare(state: Dict): running = True -tile_grid = CustomTileGrid(state) +class MockScene: + class engine: + class hass: + state: Dict = state -sprite_group: pygame.sprite.Group = pygame.sprite.Group() + +rect = pygame.Rect(0, 0, 1, 1) + +tile_grid = CustomTileGrid(MockScene(), rect) # type: ignore + +sprite_group: pygame.sprite.LayeredDirty = pygame.sprite.LayeredDirty() sprite_group.add(tile_grid) while running: @@ -120,7 +129,7 @@ def randomise_state_rare(state: Dict): print(f"State: {state}") screen.fill((0, 0, 0, 255)) - sprite_group.update() + sprite_group.update(frame, clock, 0, []) if tile_grid.rect: tile_grid.rect.topleft = (SCREEN_WIDTH - tile_grid.rect.width, 0) sprite_group.draw(screen) diff --git a/wideboy/testing/sprites/tiles.py b/wideboy/testing/tile_grid/tiles.py similarity index 99% rename from wideboy/testing/sprites/tiles.py rename to wideboy/testing/tile_grid/tiles.py index 82d573a..3a69c09 100644 --- a/wideboy/testing/sprites/tiles.py +++ b/wideboy/testing/tile_grid/tiles.py @@ -2,7 +2,8 @@ import enum import pygame import random -from .lib.tile_grid import ( + +from wideboy.sprites.tile_grid import ( TileGrid, HorizontalCollapseTileGridColumn, VerticalCollapseTileGridCell,