diff --git a/main.py b/main.py index 9a05fa3..193cb80 100644 --- a/main.py +++ b/main.py @@ -105,7 +105,6 @@ class App: slider_width = 50 # 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_hex_color = initial_colors[0] if initial_colors else "#000000" 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.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.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) + + # 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: - 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 --- 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 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)): return @@ -343,6 +350,11 @@ class App: 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): """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 @@ -351,6 +363,8 @@ class App: messagebox.showwarning("Color Limit Reached", f"You can add a maximum of {MAX_COLORS} colors to the palette.") 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 self.refresh_color_palette_display(tab) # Select the newly added color @@ -369,7 +383,7 @@ class App: # Adjust selected index if the removed color was the last one if current_index >= len(tab.colors_in_palette): 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 self.refresh_color_palette_display(tab) @@ -386,37 +400,37 @@ class App: def update_ui_for_pattern(self, tab, current_pattern: str): """ Manages the state of the UI elements based on the selected pattern. - The Color Palette Editor is now always visible when 'transition' is selected, - and RGB sliders update based on the selected color in the palette. - When not 'transition', RGB sliders revert to the first color in settings. + The Color Palette Editor is always visible. RGB sliders update + based on the currently selected color in the palette, or the first + 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 - # 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. + # The color_palette_editor_frame is always packed, so no visibility control needed here. - # If the pattern is "transition", select the current color in the palette - # and ensure the RGB sliders reflect that color. - if current_pattern == "transition": - # This handles refreshing the display and setting sliders to the selected color - self.refresh_color_palette_display(tab) - self.select_color_in_palette(tab, tab.widgets["selected_color_index"]) + # When the pattern changes, we need to ensure the RGB sliders reflect + # the appropriate color based on the context. + + if tab.colors_in_palette: + # If in 'transition' mode, set sliders to the currently selected color in the palette. + 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: - # When switching away from transition, ensure RGB sliders show the first color from settings - # and reset selected_color_index. - initial_colors = self.settings["lights"][self.notebook.tab(self.notebook.select(), "text")]["settings"].get( - "colors", ["#000000"] - ) - initial_hex_color = initial_colors[0] if initial_colors else "#000000" - 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) + # Handle empty palette scenario (shouldn't happen with default ["#000000"]) + tab.widgets["red_slider"].set(0) + tab.widgets["green_slider"].set(0) + tab.widgets["blue_slider"].set(0) + tab.widgets["selected_color_index"] = 0 # Ensure index is valid + self._highlight_selected_color_swatch(tab) # Brightness and Delay sliders are always visible. @@ -557,38 +571,42 @@ class App: selected_server = self.notebook.tab(self.notebook.select(), "text") names = self.settings["lights"][selected_server]["names"] - # ALWAYS send the full current palette, or at least the first color, - # along with other relevant settings, when an RGB slider is moved. - # The device firmware will interpret 'colors' based on its current pattern. + # Determine which colors to send based on the current pattern. + current_pattern = self.settings["lights"][selected_server]["settings"].get("pattern", "on") + colors_to_send = [] - # Determine which colors to send. It's generally safest to send the - # full current palette, as the device might need all of them. - colors_to_send = tab.colors_in_palette.copy() # Send a copy to be safe + if current_pattern == "transition": + colors_to_send = tab.colors_in_palette.copy() + 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 = { "save": True, # Always save this change to config "names": names, "settings": { - "colors": colors_to_send, - # 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. + "colors": colors_to_send, # This now dynamically changes based on pattern "brightness": tab.widgets["brightness_slider"].get(), "delay": tab.widgets["delay_slider"].get(), - # Also include the current pattern, so the device knows how to apply colors - "pattern": self.settings["lights"][selected_server]["settings"].get("pattern", "on"), + "pattern": current_pattern, # Always send the current pattern }, } # 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() - # 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"]["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() await self.websocket_client.send_data(payload) @@ -669,18 +687,20 @@ class App: "delay": current_settings_for_tab.get("delay", 0), } - # Only include "colors" in the payload if the pattern specifically uses them - # For "transition", send the entire palette - # For "on", "off", "blink", usually just the first color from the palette is relevant + # Determine colors to send based on the *newly selected* pattern if pattern_name == "transition": - # Ensure we send the *current state* of the palette - payload_settings["colors"] = current_tab_widget.colors_in_palette + payload_settings["colors"] = current_tab_widget.colors_in_palette.copy() elif pattern_name in ["on", "blink"]: # Add other patterns that use a single color here - if current_tab_widget.colors_in_palette: - payload_settings["colors"] = [current_tab_widget.colors_in_palette[0]] - else: - payload_settings["colors"] = ["#000000"] # Default if palette is empty - # For patterns like "off" or "rainbow", "colors" might not be needed or handled differently + # When switching TO 'on' or 'blink', ensure the color sent is the one + # currently displayed on the sliders (which reflects the selected palette color). + r = current_tab_widget.widgets["red_slider"].get() + g = current_tab_widget.widgets["green_slider"].get() + 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 = { "save": True, @@ -689,15 +709,13 @@ class App: } # 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 - # Ensure the saved colors are always the palette's current state - self.settings["lights"][tab_name]["settings"]["colors"] = current_tab_widget.colors_in_palette + # Always save the full current palette state in settings. + self.settings["lights"][tab_name]["settings"]["colors"] = current_tab_widget.colors_in_palette.copy() self.settings.save() 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) print(f"Sent pattern payload: {payload}")