diff --git a/plugins.json b/plugins.json index f91bd3b..a469948 100644 --- a/plugins.json +++ b/plugins.json @@ -248,7 +248,7 @@ "last_updated": "2026-02-15", "verified": true, "screenshot": "", - "latest_version": "1.0.1" + "latest_version": "1.1.0" }, { "id": "basketball-scoreboard", @@ -299,7 +299,7 @@ "last_updated": "2026-02-15", "verified": true, "screenshot": "", - "latest_version": "2.0.1" + "latest_version": "1.3.0" }, { "id": "soccer-scoreboard", diff --git a/plugins/baseball-scoreboard/manager.py b/plugins/baseball-scoreboard/manager.py index ea03c08..af13622 100644 --- a/plugins/baseball-scoreboard/manager.py +++ b/plugins/baseball-scoreboard/manager.py @@ -709,7 +709,7 @@ def _collect_games_for_scroll( league_games = [] for mt in mode_types: # Check if scroll mode is enabled for this league/mode - if self._get_display_mode('mlb', mt) == 'scroll': + if mode_type is None or self._get_display_mode('mlb', mt) == 'scroll': league_manager = self._get_manager_for_league_mode('mlb', mt) if league_manager: league_games_list = self._get_games_from_manager(league_manager, mt) @@ -737,7 +737,7 @@ def _collect_games_for_scroll( league_games = [] for mt in mode_types: # Check if scroll mode is enabled for this league/mode - if self._get_display_mode('milb', mt) == 'scroll': + if mode_type is None or self._get_display_mode('milb', mt) == 'scroll': league_manager = self._get_manager_for_league_mode('milb', mt) if league_manager: league_games_list = self._get_games_from_manager(league_manager, mt) @@ -764,7 +764,7 @@ def _collect_games_for_scroll( league_games = [] for mt in mode_types: # Check if scroll mode is enabled for this league/mode - if self._get_display_mode('ncaa_baseball', mt) == 'scroll': + if mode_type is None or self._get_display_mode('ncaa_baseball', mt) == 'scroll': ncaa_manager = self._get_manager_for_league_mode('ncaa_baseball', mt) if ncaa_manager: ncaa_games = self._get_games_from_manager(ncaa_manager, mt) @@ -2549,7 +2549,7 @@ def get_info(self) -> Dict[str, Any]: info = { "plugin_id": self.plugin_id, "name": "Baseball Scoreboard", - "version": "2.0.0", + "version": "1.3.0", "enabled": self.is_enabled, "display_size": f"{self.display_width}x{self.display_height}", "mlb_enabled": self.mlb_enabled, diff --git a/plugins/baseball-scoreboard/manifest.json b/plugins/baseball-scoreboard/manifest.json index 70e763b..0c7f516 100644 --- a/plugins/baseball-scoreboard/manifest.json +++ b/plugins/baseball-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "baseball-scoreboard", "name": "Baseball Scoreboard", - "version": "2.0.1", + "version": "1.3.0", "author": "ChuckBuilds", "description": "Live, recent, and upcoming baseball games across MLB, MiLB, and NCAA Baseball with real-time scores and schedules", "category": "sports", @@ -31,8 +31,8 @@ "plugin_path": "plugins/baseball-scoreboard", "versions": [ { - "released": "2026-02-14", - "version": "2.0.0", + "released": "2026-02-15", + "version": "1.3.0", "ledmatrix_min": "2.0.0" }, { diff --git a/plugins/baseball-scoreboard/scroll_display.py b/plugins/baseball-scoreboard/scroll_display.py index c7cfea0..ad3dbeb 100644 --- a/plugins/baseball-scoreboard/scroll_display.py +++ b/plugins/baseball-scoreboard/scroll_display.py @@ -317,6 +317,7 @@ def prepare_scroll_content( self.logger.debug("No games to prepare for scrolling") self.scroll_helper.clear_cache() self._current_games = [] + self._vegas_content_items = [] self._is_scrolling = False return False @@ -510,6 +511,7 @@ def clear(self) -> None: self._current_games = [] self._current_game_type = "" self._current_leagues = [] + self._vegas_content_items = [] self._is_scrolling = False self._scroll_start_time = None self.logger.debug("Scroll display cleared") diff --git a/plugins/basketball-scoreboard/manifest.json b/plugins/basketball-scoreboard/manifest.json index 3505db3..46ffb76 100644 --- a/plugins/basketball-scoreboard/manifest.json +++ b/plugins/basketball-scoreboard/manifest.json @@ -18,6 +18,11 @@ "branch": "main", "plugin_path": "plugins/basketball-scoreboard", "versions": [ + { + "version": "1.1.1", + "ledmatrix_min": "2.0.0", + "released": "2026-02-15" + }, { "version": "1.1.0", "ledmatrix_min": "2.0.0", @@ -31,7 +36,7 @@ ], "stars": 0, "downloads": 0, - "last_updated": "2026-02-13", + "last_updated": "2026-02-15", "verified": true, "screenshot": "", "display_modes": [ diff --git a/plugins/basketball-scoreboard/scroll_display.py b/plugins/basketball-scoreboard/scroll_display.py index 85cfb8d..7cdf712 100644 --- a/plugins/basketball-scoreboard/scroll_display.py +++ b/plugins/basketball-scoreboard/scroll_display.py @@ -311,6 +311,7 @@ def prepare_scroll_content( if not games: self.logger.debug("No games to prepare for scrolling") self.scroll_helper.clear_cache() + self._vegas_content_items = [] return False self._current_games = games @@ -525,6 +526,7 @@ def clear(self) -> None: self._current_games = [] self._current_game_type = "" self._current_leagues = [] + self._vegas_content_items = [] self._is_scrolling = False self._scroll_start_time = None self.logger.debug("Scroll display cleared") diff --git a/plugins/football-scoreboard/manager.py b/plugins/football-scoreboard/manager.py index 5804c84..07253d0 100644 --- a/plugins/football-scoreboard/manager.py +++ b/plugins/football-scoreboard/manager.py @@ -677,7 +677,7 @@ def _collect_games_for_scroll( league_games = [] for mt in mode_types: # Check if scroll mode is enabled for this league/mode - if self._get_display_mode('nfl', mt) == 'scroll': + if mode_type is None or self._get_display_mode('nfl', mt) == 'scroll': nfl_manager = self._get_manager_for_league_mode('nfl', mt) if nfl_manager: nfl_games = self._get_games_from_manager(nfl_manager, mt) @@ -704,7 +704,7 @@ def _collect_games_for_scroll( league_games = [] for mt in mode_types: # Check if scroll mode is enabled for this league/mode - if self._get_display_mode('ncaa_fb', mt) == 'scroll': + if mode_type is None or self._get_display_mode('ncaa_fb', mt) == 'scroll': ncaa_manager = self._get_manager_for_league_mode('ncaa_fb', mt) if ncaa_manager: ncaa_games = self._get_games_from_manager(ncaa_manager, mt) @@ -2323,7 +2323,7 @@ def get_info(self) -> Dict[str, Any]: info = { "plugin_id": self.plugin_id, "name": "Football Scoreboard", - "version": "2.0.5", + "version": "2.1.1", "enabled": self.is_enabled, "display_size": f"{self.display_width}x{self.display_height}", "nfl_enabled": self.nfl_enabled, @@ -3387,7 +3387,7 @@ def get_vegas_content(self) -> Optional[Any]: images = self._scroll_manager.get_all_vegas_content_items() - if not images: + if not images or 'mixed' not in self._scroll_manager._scroll_displays: self.logger.info("[Football Vegas] Triggering scroll content generation") self._ensure_scroll_content_for_vegas() images = self._scroll_manager.get_all_vegas_content_items() diff --git a/plugins/football-scoreboard/manifest.json b/plugins/football-scoreboard/manifest.json index acb1b27..01b3cfb 100644 --- a/plugins/football-scoreboard/manifest.json +++ b/plugins/football-scoreboard/manifest.json @@ -24,6 +24,11 @@ "ncaa_fb_live" ], "versions": [ + { + "version": "2.1.1", + "ledmatrix_min": "2.0.0", + "released": "2026-02-15" + }, { "version": "2.1.0", "ledmatrix_min": "2.0.0", @@ -205,7 +210,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2025-11-05", + "last_updated": "2026-02-15", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/football-scoreboard/scroll_display.py b/plugins/football-scoreboard/scroll_display.py index 6a04afc..becaa9f 100644 --- a/plugins/football-scoreboard/scroll_display.py +++ b/plugins/football-scoreboard/scroll_display.py @@ -280,6 +280,7 @@ def prepare_scroll_content( if not games: self.logger.debug("No games to prepare for scrolling") self.scroll_helper.clear_cache() + self._vegas_content_items = [] return False self._current_games = games @@ -489,6 +490,7 @@ def clear(self) -> None: self._current_games = [] self._current_game_type = "" self._current_leagues = [] + self._vegas_content_items = [] self._is_scrolling = False self._scroll_start_time = None self.logger.debug("Scroll display cleared") diff --git a/plugins/hockey-scoreboard/manager.py b/plugins/hockey-scoreboard/manager.py index 284cf77..81760b0 100644 --- a/plugins/hockey-scoreboard/manager.py +++ b/plugins/hockey-scoreboard/manager.py @@ -2914,7 +2914,7 @@ def get_info(self) -> Dict[str, Any]: info = { "plugin_id": self.plugin_id, "name": "Hockey Scoreboard", - "version": "1.0.0", + "version": "1.1.1", "enabled": self.is_enabled, "display_size": f"{self.display_width}x{self.display_height}", "nhl_enabled": self.nhl_enabled, diff --git a/plugins/hockey-scoreboard/manifest.json b/plugins/hockey-scoreboard/manifest.json index 720d6ab..1dd3b58 100644 --- a/plugins/hockey-scoreboard/manifest.json +++ b/plugins/hockey-scoreboard/manifest.json @@ -55,6 +55,11 @@ } ], "versions": [ + { + "version": "1.1.1", + "ledmatrix_min": "2.0.0", + "released": "2026-02-15" + }, { "version": "1.1.0", "ledmatrix_min": "2.0.0", @@ -101,7 +106,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2025-11-06", + "last_updated": "2026-02-15", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/hockey-scoreboard/scroll_display.py b/plugins/hockey-scoreboard/scroll_display.py index 9a8d278..5a49291 100644 --- a/plugins/hockey-scoreboard/scroll_display.py +++ b/plugins/hockey-scoreboard/scroll_display.py @@ -517,6 +517,7 @@ def clear(self) -> None: self._current_games = [] self._current_game_type = "" self._current_leagues = [] + self._vegas_content_items = [] self._is_scrolling = False self._scroll_start_time = None self.logger.debug("Scroll display cleared") diff --git a/plugins/ledmatrix-weather/manager.py b/plugins/ledmatrix-weather/manager.py index e270020..f06369f 100644 --- a/plugins/ledmatrix-weather/manager.py +++ b/plugins/ledmatrix-weather/manager.py @@ -143,12 +143,6 @@ def __init__(self, plugin_id: str, config: Dict[str, Any], # Layout constants self.PADDING = 1 - self.ICON_SIZE = { - 'extra_large': 40, - 'large': 30, - 'medium': 24, - 'small': 14 - } self.COLORS = { 'text': (255, 255, 255), 'highlight': (255, 200, 0), @@ -213,7 +207,64 @@ def _register_fonts(self): self.logger.info("Weather plugin fonts registered successfully") except Exception as e: self.logger.warning(f"Error registering fonts: {e}") - + + def _get_layout(self) -> dict: + """Return cached layout parameters (computed once on first call). + + Icon sizes scale proportionally with display height. + Text spacing stays fixed because fonts are fixed-size bitmaps. + Reference baseline: 128x32 display. + """ + if hasattr(self, '_layout_cache'): + return self._layout_cache + + width = self.display_manager.matrix.width + height = self.display_manager.matrix.height + h_scale = height / 32.0 + + # Fixed font metrics (do not change with display size) + small_font_h = 8 + extra_small_font_h = 7 + + margin = max(1, round(1 * h_scale)) + + # --- Current weather mode --- + current_icon_size = max(14, round(40 * h_scale)) + current_icon_x = margin + current_available_h = (height * 2) // 3 + current_icon_y = (current_available_h - current_icon_size) // 2 + + # Text rows on right side (fixed spacing since fonts are fixed) + condition_y = margin + temp_y = condition_y + small_font_h + high_low_y = temp_y + small_font_h + bottom_bar_y = height - extra_small_font_h + + # --- Forecast modes (hourly + daily) --- + # Scale with height but cap by narrowest column width to prevent overflow + min_column_width = width // 4 + forecast_icon_size = max(14, min(round(30 * h_scale), min_column_width)) + forecast_top_y = margin + forecast_icon_y = max(0, (height - forecast_icon_size) // 2) + forecast_bottom_y = height - small_font_h + + self._layout_cache = { + 'current_icon_size': current_icon_size, + 'current_icon_x': current_icon_x, + 'current_icon_y': current_icon_y, + 'condition_y': condition_y, + 'temp_y': temp_y, + 'high_low_y': high_low_y, + 'bottom_bar_y': bottom_bar_y, + 'right_margin': margin, + 'forecast_icon_size': forecast_icon_size, + 'forecast_top_y': forecast_top_y, + 'forecast_icon_y': forecast_icon_y, + 'forecast_bottom_y': forecast_bottom_y, + 'margin': margin, + } + return self._layout_cache + def update(self) -> None: """ Update weather data from OpenWeatherMap API. @@ -555,39 +606,40 @@ def _render_current_weather_image(self) -> Optional[Image.Image]: temp_high = int(self.weather_data['main']['temp_max']) temp_low = int(self.weather_data['main']['temp_min']) + layout = self._get_layout() + # --- Top Left: Weather Icon --- - icon_size = self.ICON_SIZE['extra_large'] - icon_x = 1 - available_height = (height * 2) // 3 - icon_y = (available_height - icon_size) // 2 + icon_size = layout['current_icon_size'] + icon_x = layout['current_icon_x'] + icon_y = layout['current_icon_y'] WeatherIcons.draw_weather_icon(img, icon_code, icon_x, icon_y, size=icon_size) # --- Top Right: Condition Text --- condition_font = self.display_manager.small_font condition_text_width = draw.textlength(condition, font=condition_font) - condition_x = width - condition_text_width - 1 - condition_y = 1 + condition_x = width - condition_text_width - layout['right_margin'] + condition_y = layout['condition_y'] draw.text((condition_x, condition_y), condition, font=condition_font, fill=self.COLORS['text']) # --- Right Side: Current Temperature --- temp_text = f"{temp}°" temp_font = self.display_manager.small_font temp_text_width = draw.textlength(temp_text, font=temp_font) - temp_x = width - temp_text_width - 1 - temp_y = condition_y + 8 + temp_x = width - temp_text_width - layout['right_margin'] + temp_y = layout['temp_y'] draw.text((temp_x, temp_y), temp_text, font=temp_font, fill=self.COLORS['highlight']) # --- Right Side: High/Low Temperature --- high_low_text = f"{temp_low}°/{temp_high}°" high_low_font = self.display_manager.small_font high_low_width = draw.textlength(high_low_text, font=high_low_font) - high_low_x = width - high_low_width - 1 - high_low_y = temp_y + 8 + high_low_x = width - high_low_width - layout['right_margin'] + high_low_y = layout['high_low_y'] draw.text((high_low_x, high_low_y), high_low_text, font=high_low_font, fill=self.COLORS['dim']) # --- Bottom: Additional Metrics --- section_width = width // 3 - y_pos = height - 7 + y_pos = layout['bottom_bar_y'] font = self.display_manager.extra_small_font # UV Index (Section 1) @@ -700,6 +752,7 @@ def _render_hourly_forecast_image(self) -> Optional[Image.Image]: img = Image.new('RGB', (width, height), (0, 0, 0)) draw = ImageDraw.Draw(img) + layout = self._get_layout() hours_to_show = min(4, len(self.hourly_forecast)) section_width = width // hours_to_show padding = max(2, section_width // 6) @@ -713,21 +766,21 @@ def _render_hourly_forecast_image(self) -> Optional[Image.Image]: hour_text = forecast['hour'] hour_text = hour_text.replace(":00 ", "").replace("PM", "p").replace("AM", "a") hour_width = draw.textlength(hour_text, font=self.display_manager.small_font) - draw.text((center_x - hour_width // 2, 1), + draw.text((center_x - hour_width // 2, layout['forecast_top_y']), hour_text, font=self.display_manager.small_font, fill=self.COLORS['text']) # Weather icon - icon_size = self.ICON_SIZE['large'] - icon_y = (height // 2) - 16 + icon_size = layout['forecast_icon_size'] + icon_y = layout['forecast_icon_y'] icon_x = center_x - icon_size // 2 WeatherIcons.draw_weather_icon(img, forecast['icon'], icon_x, icon_y, icon_size) # Temperature at bottom temp_text = f"{forecast['temp']}°" temp_width = draw.textlength(temp_text, font=self.display_manager.small_font) - temp_y = height - 8 + temp_y = layout['forecast_bottom_y'] draw.text((center_x - temp_width // 2, temp_y), temp_text, font=self.display_manager.small_font, @@ -771,6 +824,7 @@ def _render_daily_forecast_image(self) -> Optional[Image.Image]: img = Image.new('RGB', (width, height), (0, 0, 0)) draw = ImageDraw.Draw(img) + layout = self._get_layout() days_to_show = min(3, len(self.daily_forecast)) if days_to_show == 0: draw.text((2, 2), "No daily forecast", font=self.display_manager.small_font, fill=self.COLORS['dim']) @@ -784,21 +838,21 @@ def _render_daily_forecast_image(self) -> Optional[Image.Image]: # Day name at top day_text = forecast['date'] day_width = draw.textlength(day_text, font=self.display_manager.small_font) - draw.text((center_x - day_width // 2, 1), + draw.text((center_x - day_width // 2, layout['forecast_top_y']), day_text, font=self.display_manager.small_font, fill=self.COLORS['text']) # Weather icon - icon_size = self.ICON_SIZE['large'] - icon_y = (height // 2) - 16 + icon_size = layout['forecast_icon_size'] + icon_y = layout['forecast_icon_y'] icon_x = center_x - icon_size // 2 WeatherIcons.draw_weather_icon(img, forecast['icon'], icon_x, icon_y, icon_size) # High/low temperatures at bottom temp_text = f"{forecast['temp_low']} / {forecast['temp_high']}" temp_width = draw.textlength(temp_text, font=self.display_manager.extra_small_font) - temp_y = height - 8 + temp_y = layout['forecast_bottom_y'] draw.text((center_x - temp_width // 2, temp_y), temp_text, font=self.display_manager.extra_small_font, diff --git a/plugins/ledmatrix-weather/manifest.json b/plugins/ledmatrix-weather/manifest.json index 5c7c923..d355ccc 100644 --- a/plugins/ledmatrix-weather/manifest.json +++ b/plugins/ledmatrix-weather/manifest.json @@ -23,6 +23,11 @@ "daily_forecast" ], "versions": [ + { + "version": "2.1.1", + "ledmatrix_min": "2.0.0", + "released": "2026-02-15" + }, { "version": "2.1.0", "ledmatrix_min": "2.0.0", @@ -49,7 +54,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-02-13", + "last_updated": "2026-02-15", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/soccer-scoreboard/manager.py b/plugins/soccer-scoreboard/manager.py index 18a65c1..4958c3e 100644 --- a/plugins/soccer-scoreboard/manager.py +++ b/plugins/soccer-scoreboard/manager.py @@ -1002,7 +1002,7 @@ def _collect_games_for_scroll( for league_key in ordered_leagues: for mt in mode_types: - if self._get_display_mode(league_key, mt) != 'scroll': + if mode_type is not None and self._get_display_mode(league_key, mt) != 'scroll': continue manager = self._get_league_manager_for_mode(league_key, mt) @@ -1684,7 +1684,7 @@ def get_info(self) -> Dict[str, Any]: info = { "plugin_id": self.plugin_id, "name": "Soccer Scoreboard", - "version": "2.0.0", + "version": "1.3.1", "enabled": self.is_enabled, "display_size": f"{self.display_width}x{self.display_height}", "leagues": league_info, diff --git a/plugins/soccer-scoreboard/manifest.json b/plugins/soccer-scoreboard/manifest.json index 170e569..e52c631 100644 --- a/plugins/soccer-scoreboard/manifest.json +++ b/plugins/soccer-scoreboard/manifest.json @@ -24,6 +24,11 @@ "soccer_upcoming" ], "versions": [ + { + "version": "1.3.1", + "ledmatrix_min": "2.0.0", + "released": "2026-02-15" + }, { "released": "2026-02-14", "version": "1.3.0", @@ -35,7 +40,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2025-10-19", + "last_updated": "2026-02-15", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/soccer-scoreboard/scroll_display.py b/plugins/soccer-scoreboard/scroll_display.py index caaceb2..e72a9c1 100644 --- a/plugins/soccer-scoreboard/scroll_display.py +++ b/plugins/soccer-scoreboard/scroll_display.py @@ -275,6 +275,7 @@ def prepare_scroll_content( if not games: self.logger.debug("No games to prepare for scrolling") self.scroll_helper.clear_cache() + self._vegas_content_items = [] return False self._current_games = games @@ -464,6 +465,7 @@ def clear_cache(self) -> None: self._current_games = [] self._current_game_type = "" self._current_leagues = [] + self._vegas_content_items = [] self._is_scrolling = False def has_content(self) -> bool: diff --git a/plugins/ufc-scoreboard/manifest.json b/plugins/ufc-scoreboard/manifest.json index 187742e..9d785c6 100644 --- a/plugins/ufc-scoreboard/manifest.json +++ b/plugins/ufc-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "ufc-scoreboard", "name": "UFC Scoreboard", - "version": "1.0.1", + "version": "1.1.0", "author": "ChuckBuilds", "contributors": [ { @@ -25,13 +25,23 @@ "default_duration": 15, "config_schema": "config_schema.json", "versions": [ + { + "version": "1.1.0", + "ledmatrix_min": "2.0.0", + "released": "2026-02-15" + }, + { + "version": "1.0.1", + "ledmatrix_min": "2.0.0", + "released": "2026-02-14" + }, { "version": "1.0.0", "ledmatrix_min": "2.0.0", "released": "2026-02-12" } ], - "last_updated": "2026-02-12", + "last_updated": "2026-02-15", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/ufc-scoreboard/scroll_display.py b/plugins/ufc-scoreboard/scroll_display.py index 42122e4..79aa94c 100644 --- a/plugins/ufc-scoreboard/scroll_display.py +++ b/plugins/ufc-scoreboard/scroll_display.py @@ -229,6 +229,7 @@ def prepare_and_display( if not fights: self.logger.debug("No fights to prepare for scroll") + self._vegas_content_items = [] return False scroll_settings = self._get_scroll_settings() @@ -380,6 +381,7 @@ def reset(self) -> None: """Reset scroll state.""" self._is_scrolling = False self._current_fights = [] + self._vegas_content_items = [] self._frame_count = 0 if self.scroll_helper: self.scroll_helper.reset()