Intial version

This commit is contained in:
Jimmy 2025-07-12 01:18:04 +12:00
parent c77fd30f8f
commit f7f7acbdbf
1 changed files with 83 additions and 65 deletions

148
main.py
View File

@ -105,7 +105,6 @@ class App:
slider_width = 50 slider_width = 50
# Extract initial color, brightness, and delay # Extract initial color, brightness, and delay
# The 'colors' entry can now be a list. We'll pick the first one for initial display.
initial_colors = initial_settings.get("colors", ["#000000"]) initial_colors = initial_settings.get("colors", ["#000000"])
initial_hex_color = initial_colors[0] if initial_colors else "#000000" initial_hex_color = initial_colors[0] if initial_colors else "#000000"
initial_brightness = initial_settings.get("brightness", 127) initial_brightness = initial_settings.get("brightness", 127)
@ -200,12 +199,19 @@ class App:
right_panel_frame = tk.Frame(main_tab_frame, bg=bg_color) right_panel_frame = tk.Frame(main_tab_frame, bg=bg_color)
right_panel_frame.pack(side=tk.LEFT, padx=20, pady=10, anchor="n", expand=True, fill="both") right_panel_frame.pack(side=tk.LEFT, padx=20, pady=10, anchor="n", expand=True, fill="both")
# IDs section (unchanged) # IDs section - MODIFIED TO BE SIDE-BY-SIDE
ids_frame = tk.Frame(right_panel_frame, bg=bg_color) ids_frame = tk.Frame(right_panel_frame, bg=bg_color)
ids_frame.pack(pady=10, fill=tk.X) ids_frame.pack(pady=10, fill=tk.X)
tk.Label(ids_frame, text="Associated Names:", font=("Arial", 20), bg=bg_color, fg=fg_color).pack(pady=10) tk.Label(ids_frame, text="Associated Names:", font=("Arial", 20), bg=bg_color, fg=fg_color).pack(pady=10)
# New inner frame for the IDs to be displayed horizontally
ids_inner_frame = tk.Frame(ids_frame, bg=bg_color)
ids_inner_frame.pack(fill=tk.X, expand=True) # Pack this frame to fill available width
for light_id in ids: for light_id in ids:
tk.Label(ids_frame, text=str(light_id), font=("Arial", 18), bg=bg_color, fg=fg_color).pack(pady=2) tk.Label(ids_inner_frame, text=str(light_id), font=("Arial", 18), bg=bg_color, fg=fg_color).pack(
side=tk.LEFT, padx=5, pady=2
) # Pack labels horizontally
# --- New Frame to hold Patterns and Color Palette side-by-side --- # --- New Frame to hold Patterns and Color Palette side-by-side ---
patterns_and_palette_frame = tk.Frame(right_panel_frame, bg=bg_color) patterns_and_palette_frame = tk.Frame(right_panel_frame, bg=bg_color)
@ -327,7 +333,8 @@ class App:
swatch_frame.config(highlightbackground=swatch_frame.cget("bg"), highlightthickness=0) # Reset to no highlight swatch_frame.config(highlightbackground=swatch_frame.cget("bg"), highlightthickness=0) # Reset to no highlight
def select_color_in_palette(self, tab, index: int): def select_color_in_palette(self, tab, index: int):
"""Selects a color in the palette, updates sliders, and highlights swatch.""" """Selects a color in the palette, updates sliders, and highlights swatch.
This now also triggers an RGB update to the device."""
if not (0 <= index < len(tab.colors_in_palette)): if not (0 <= index < len(tab.colors_in_palette)):
return return
@ -343,6 +350,11 @@ class App:
print(f"Selected color index {index}: {hex_color}") print(f"Selected color index {index}: {hex_color}")
# Immediately send the update, as changing the selected color implies
# a desire to change the light's current color, regardless of pattern.
# This will also save the settings.
self.schedule_update_rgb(tab, force_send=True)
def add_color_to_palette(self, tab): def add_color_to_palette(self, tab):
"""Adds a new black color to the palette and selects it, with a limit of 10 colors.""" """Adds a new black color to the palette and selects it, with a limit of 10 colors."""
MAX_COLORS = 10 # Define the maximum number of colors allowed MAX_COLORS = 10 # Define the maximum number of colors allowed
@ -351,6 +363,8 @@ class App:
messagebox.showwarning("Color Limit Reached", f"You can add a maximum of {MAX_COLORS} colors to the palette.") messagebox.showwarning("Color Limit Reached", f"You can add a maximum of {MAX_COLORS} colors to the palette.")
return return
# Simplified: just add black. If unique colors were required globally,
# more complex logic would be needed here.
tab.colors_in_palette.append("#000000") # Add black as default tab.colors_in_palette.append("#000000") # Add black as default
self.refresh_color_palette_display(tab) self.refresh_color_palette_display(tab)
# Select the newly added color # Select the newly added color
@ -369,7 +383,7 @@ class App:
# Adjust selected index if the removed color was the last one # Adjust selected index if the removed color was the last one
if current_index >= len(tab.colors_in_palette): if current_index >= len(tab.colors_in_palette):
tab.widgets["selected_color_index"] = len(tab.colors_in_palette) - 1 tab.widgets["selected_color_index"] = len(tab.colors_in_palette) - 1
if tab.widgets["selected_color_index"] < 0: # Should not happen if check above works if tab.widgets["selected_color_index"] < 0: # Should not happen with 1-color check
tab.widgets["selected_color_index"] = 0 tab.widgets["selected_color_index"] = 0
self.refresh_color_palette_display(tab) self.refresh_color_palette_display(tab)
@ -386,37 +400,37 @@ class App:
def update_ui_for_pattern(self, tab, current_pattern: str): def update_ui_for_pattern(self, tab, current_pattern: str):
""" """
Manages the state of the UI elements based on the selected pattern. Manages the state of the UI elements based on the selected pattern.
The Color Palette Editor is now always visible when 'transition' is selected, The Color Palette Editor is always visible. RGB sliders update
and RGB sliders update based on the selected color in the palette. based on the currently selected color in the palette, or the first
When not 'transition', RGB sliders revert to the first color in settings. color if the palette is empty or not in transition mode and a new tab/pattern is selected.
""" """
# The color_palette_editor_frame is now *always* packed in create_light_control_widgets # The color_palette_editor_frame is always packed, so no visibility control needed here.
# when patterns_and_palette_frame is created with side-by-side packing.
# So we no longer need to pack/pack_forget it here.
# Its visibility is handled by its initial creation and packing alongside the patterns.
# If the pattern is "transition", select the current color in the palette # When the pattern changes, we need to ensure the RGB sliders reflect
# and ensure the RGB sliders reflect that color. # the appropriate color based on the context.
if current_pattern == "transition":
# This handles refreshing the display and setting sliders to the selected color if tab.colors_in_palette:
self.refresh_color_palette_display(tab) # If in 'transition' mode, set sliders to the currently selected color in the palette.
self.select_color_in_palette(tab, tab.widgets["selected_color_index"]) if current_pattern == "transition":
self.select_color_in_palette(tab, tab.widgets["selected_color_index"])
else:
# If not in 'transition' mode, but a color is selected, update sliders to that.
# Or, if this is a fresh load/tab change, ensure it's the first color.
# This ensures the sliders consistently show the color that will be sent
# for 'on'/'blink' based on the palette's first entry.
hex_color = tab.colors_in_palette[tab.widgets["selected_color_index"]]
r, g, b = color_utils.hex_to_rgb(hex_color)
tab.widgets["red_slider"].set(r)
tab.widgets["green_slider"].set(g)
tab.widgets["blue_slider"].set(b)
self._highlight_selected_color_swatch(tab) # Re-highlight even if index didn't change
else: else:
# When switching away from transition, ensure RGB sliders show the first color from settings # Handle empty palette scenario (shouldn't happen with default ["#000000"])
# and reset selected_color_index. tab.widgets["red_slider"].set(0)
initial_colors = self.settings["lights"][self.notebook.tab(self.notebook.select(), "text")]["settings"].get( tab.widgets["green_slider"].set(0)
"colors", ["#000000"] tab.widgets["blue_slider"].set(0)
) tab.widgets["selected_color_index"] = 0 # Ensure index is valid
initial_hex_color = initial_colors[0] if initial_colors else "#000000" self._highlight_selected_color_swatch(tab)
r, g, b = color_utils.hex_to_rgb(initial_hex_color)
tab.widgets["red_slider"].set(r)
tab.widgets["green_slider"].set(g)
tab.widgets["blue_slider"].set(b)
tab.widgets["selected_color_index"] = 0 # Reset selected color index to the first (default)
self._highlight_selected_color_swatch(
tab
) # Remove highlight if active color is no longer relevant for editing
# (or just highlight the 0th if you want)
# Brightness and Delay sliders are always visible. # Brightness and Delay sliders are always visible.
@ -557,38 +571,42 @@ class App:
selected_server = self.notebook.tab(self.notebook.select(), "text") selected_server = self.notebook.tab(self.notebook.select(), "text")
names = self.settings["lights"][selected_server]["names"] names = self.settings["lights"][selected_server]["names"]
# ALWAYS send the full current palette, or at least the first color, # Determine which colors to send based on the current pattern.
# along with other relevant settings, when an RGB slider is moved. current_pattern = self.settings["lights"][selected_server]["settings"].get("pattern", "on")
# The device firmware will interpret 'colors' based on its current pattern. colors_to_send = []
# Determine which colors to send. It's generally safest to send the if current_pattern == "transition":
# full current palette, as the device might need all of them. colors_to_send = tab.colors_in_palette.copy()
colors_to_send = tab.colors_in_palette.copy() # Send a copy to be safe elif current_pattern in ["on", "blink"]: # Add other patterns that use a single color
if tab.colors_in_palette:
# For non-transition patterns, the device typically uses only the first color.
# However, if a user picks a color from the palette, we want THAT color to be the one
# sent and active. So, the selected color from the palette *becomes* the first color
# in the list we send to the device for these modes.
# This ensures the light matches the selected palette color.
colors_to_send = [hex_color] # Send the color currently set by the sliders
else:
colors_to_send = ["#000000"] # Default if palette is empty
else: # For other patterns like "off", "rainbow" where colors might not be primary
# We still want to send the *current* palette state for saving,
# but the device firmware might ignore it for these patterns.
colors_to_send = tab.colors_in_palette.copy()
payload = { payload = {
"save": True, # Always save this change to config "save": True, # Always save this change to config
"names": names, "names": names,
"settings": { "settings": {
"colors": colors_to_send, "colors": colors_to_send, # This now dynamically changes based on pattern
# We might also want to send the current brightness and delay
# to ensure the device has the complete state, or at least
# ensures these don't get 'unset' if they weren't explicitly changed.
# This depends on your firmware's expected payload.
"brightness": tab.widgets["brightness_slider"].get(), "brightness": tab.widgets["brightness_slider"].get(),
"delay": tab.widgets["delay_slider"].get(), "delay": tab.widgets["delay_slider"].get(),
# Also include the current pattern, so the device knows how to apply colors "pattern": current_pattern, # Always send the current pattern
"pattern": self.settings["lights"][selected_server]["settings"].get("pattern", "on"),
}, },
} }
# Update the settings object with the new color list (and potentially other synced values) # Update the settings object with the new color list (and potentially other synced values)
self.settings["lights"][selected_server]["settings"]["colors"] = tab.colors_in_palette.copy() self.settings["lights"][selected_server]["settings"]["colors"] = tab.colors_in_palette.copy()
# Also ensure brightness, delay, and pattern are up-to-date in settings before saving
self.settings["lights"][selected_server]["settings"]["brightness"] = tab.widgets["brightness_slider"].get() self.settings["lights"][selected_server]["settings"]["brightness"] = tab.widgets["brightness_slider"].get()
self.settings["lights"][selected_server]["settings"]["delay"] = tab.widgets["delay_slider"].get() self.settings["lights"][selected_server]["settings"]["delay"] = tab.widgets["delay_slider"].get()
# Pattern is generally set by the pattern buttons, but including it in the save here
# for completeness might be useful depending on your app's state management.
# self.settings["lights"][selected_server]["settings"]["pattern"] = current_pattern # Already updated by send_pattern
self.settings.save() self.settings.save()
await self.websocket_client.send_data(payload) await self.websocket_client.send_data(payload)
@ -669,18 +687,20 @@ class App:
"delay": current_settings_for_tab.get("delay", 0), "delay": current_settings_for_tab.get("delay", 0),
} }
# Only include "colors" in the payload if the pattern specifically uses them # Determine colors to send based on the *newly selected* pattern
# For "transition", send the entire palette
# For "on", "off", "blink", usually just the first color from the palette is relevant
if pattern_name == "transition": if pattern_name == "transition":
# Ensure we send the *current state* of the palette payload_settings["colors"] = current_tab_widget.colors_in_palette.copy()
payload_settings["colors"] = current_tab_widget.colors_in_palette
elif pattern_name in ["on", "blink"]: # Add other patterns that use a single color here elif pattern_name in ["on", "blink"]: # Add other patterns that use a single color here
if current_tab_widget.colors_in_palette: # When switching TO 'on' or 'blink', ensure the color sent is the one
payload_settings["colors"] = [current_tab_widget.colors_in_palette[0]] # currently displayed on the sliders (which reflects the selected palette color).
else: r = current_tab_widget.widgets["red_slider"].get()
payload_settings["colors"] = ["#000000"] # Default if palette is empty g = current_tab_widget.widgets["green_slider"].get()
# For patterns like "off" or "rainbow", "colors" might not be needed or handled differently b = current_tab_widget.widgets["blue_slider"].get()
hex_color_from_sliders = f"#{r:02x}{g:02x}{b:02x}"
payload_settings["colors"] = [hex_color_from_sliders]
else:
# For other patterns, send the full palette, device might ignore or use default
payload_settings["colors"] = current_tab_widget.colors_in_palette.copy()
payload = { payload = {
"save": True, "save": True,
@ -689,15 +709,13 @@ class App:
} }
# Update the settings object with the new pattern and current colors/brightness/delay # Update the settings object with the new pattern and current colors/brightness/delay
# It's important to save the state that *was active* when the pattern was set,
# or the state that should *persist* with the pattern.
self.settings["lights"][tab_name]["settings"]["pattern"] = pattern_name self.settings["lights"][tab_name]["settings"]["pattern"] = pattern_name
# Ensure the saved colors are always the palette's current state # Always save the full current palette state in settings.
self.settings["lights"][tab_name]["settings"]["colors"] = current_tab_widget.colors_in_palette self.settings["lights"][tab_name]["settings"]["colors"] = current_tab_widget.colors_in_palette.copy()
self.settings.save() self.settings.save()
self.highlight_pattern_button(current_tab_widget, pattern_name) self.highlight_pattern_button(current_tab_widget, pattern_name)
self.update_ui_for_pattern(current_tab_widget, pattern_name) # Update visibility self.update_ui_for_pattern(current_tab_widget, pattern_name) # Update UI based on new pattern
await self.websocket_client.send_data(payload) await self.websocket_client.send_data(payload)
print(f"Sent pattern payload: {payload}") print(f"Sent pattern payload: {payload}")