From fb4944e47503bf09a6f84ef2eb75be2ca18cfb86 Mon Sep 17 00:00:00 2001 From: jimmy Date: Sun, 30 Nov 2025 16:44:14 +1300 Subject: [PATCH] Add screen resolution scaling and move tab buttons to bottom --- settings.json | 44 +---------- src/main.py | 213 +++++++++++++++++++++++++++++++------------------- 2 files changed, 135 insertions(+), 122 deletions(-) diff --git a/settings.json b/settings.json index bb6be11..1e10474 100644 --- a/settings.json +++ b/settings.json @@ -1,46 +1,4 @@ { "tab_password": "qwerty1234", - "current_profile": "tt", - "patterns": { - "on": { - "min_delay": 10, - "max_delay": 10000 - }, - "off": { - "min_delay": 10, - "max_delay": 10000 - }, - "rainbow": { - "Step Rate": "n1", - "min_delay": 10, - "max_delay": 10000 - }, - "transition": { - "min_delay": 10, - "max_delay": 10000 - }, - "chase": { - "Colour 1 Length": "n1", - "Colour 2 Length": "n2", - "Step 1": "n3", - "Step 2": "n4", - "min_delay": 10, - "max_delay": 10000 - }, - "pulse": { - "Attack": "n1", - "Hold": "n2", - "Decay": "n3", - "min_delay": 10, - "max_delay": 10000 - }, - "circle": { - "Head Rate": "n1", - "Max Length": "n2", - "Tail Rate": "n3", - "Min Length": "n4", - "min_delay": 10, - "max_delay": 10000 - } - } + "current_profile": "tt" } \ No newline at end of file diff --git a/src/main.py b/src/main.py index 5837595..2f2e9d2 100644 --- a/src/main.py +++ b/src/main.py @@ -55,6 +55,27 @@ class App: self.root = tk.Tk() self.root.attributes("-fullscreen", True) self.root.configure(bg=bg_color) + + # Calculate scale factor based on screen resolution + # Reference resolution: 1920x1080 + screen_width = self.root.winfo_screenwidth() + screen_height = self.root.winfo_screenheight() + ref_width = 1920 + ref_height = 1080 + # Use the smaller scale factor to ensure everything fits + self.scale_factor = min(screen_width / ref_width, screen_height / ref_height) + # Clamp scale factor between 0.5 and 2.0 for reasonable scaling + self.scale_factor = max(0.5, min(2.0, self.scale_factor)) + + # Helper methods for scaling + def scale_font(size): + return int(size * self.scale_factor) + + def scale_size(size): + return int(size * self.scale_factor) + + self.scale_font = scale_font + self.scale_size = scale_size # Debouncing variables (remain the same) self.last_rgb_update_time = 0 @@ -77,24 +98,24 @@ class App: self.websocket_client = WebSocketClient("ws://192.168.4.1:80/ws") self.root.after(100, async_handler(self.websocket_client.connect)) - # Configure ttk style (unchanged) + # Configure ttk style (scaled) style = ttk.Style() style.theme_use("alt") - style.configure(".", background=bg_color, foreground=fg_color, font=("Arial", 14)) + style.configure(".", background=bg_color, foreground=fg_color, font=("Arial", self.scale_font(14))) style.configure("TNotebook", background=bg_color, borderwidth=0) style.configure( - "TNotebook.Tab", background=bg_color, foreground=fg_color, font=("Arial", 30), padding=[10, 5] + "TNotebook.Tab", background=bg_color, foreground=fg_color, font=("Arial", self.scale_font(30)), padding=[self.scale_size(10), self.scale_size(5)] ) style.map("TNotebook.Tab", background=[("selected", active_bg_color)], foreground=[("selected", fg_color)]) style.configure("TFrame", background=bg_color) - # Create Notebook for tabs (unchanged) + # Create Notebook for tabs (packed first so it takes remaining space) self.notebook = ttk.Notebook(self.root) self.notebook.pack(expand=1, fill="both") - # Tab management buttons frame + # Tab management buttons frame (packed at bottom so it stays visible) tab_management_frame = tk.Frame(self.root, bg=bg_color) - tab_management_frame.pack(side=tk.TOP, fill=tk.X, padx=10, pady=5) + tab_management_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=self.scale_size(10), pady=self.scale_size(5)) add_tab_btn = tk.Button( tab_management_frame, @@ -102,11 +123,11 @@ class App: command=self.add_tab_dialog, bg=active_bg_color, fg=fg_color, - font=("Arial", 14), - padx=10, - pady=5 + font=("Arial", self.scale_font(14)), + padx=self.scale_size(10), + pady=self.scale_size(5) ) - add_tab_btn.pack(side=tk.LEFT, padx=5) + add_tab_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) edit_tab_btn = tk.Button( tab_management_frame, @@ -114,11 +135,11 @@ class App: command=self.edit_tab_dialog, bg=active_bg_color, fg=fg_color, - font=("Arial", 14), - padx=10, - pady=5 + font=("Arial", self.scale_font(14)), + padx=self.scale_size(10), + pady=self.scale_size(5) ) - edit_tab_btn.pack(side=tk.LEFT, padx=5) + edit_tab_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) delete_tab_btn = tk.Button( tab_management_frame, @@ -126,11 +147,11 @@ class App: command=self.delete_tab_dialog, bg=active_bg_color, fg=fg_color, - font=("Arial", 14), - padx=10, - pady=5 + font=("Arial", self.scale_font(14)), + padx=self.scale_size(10), + pady=self.scale_size(5) ) - delete_tab_btn.pack(side=tk.LEFT, padx=5) + delete_tab_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) # Tab reorder buttons move_left_btn = tk.Button( @@ -139,11 +160,11 @@ class App: command=self.move_tab_left, bg=active_bg_color, fg=fg_color, - font=("Arial", 14), - padx=10, - pady=5 + font=("Arial", self.scale_font(14)), + padx=self.scale_size(10), + pady=self.scale_size(5) ) - move_left_btn.pack(side=tk.LEFT, padx=5) + move_left_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) move_right_btn = tk.Button( tab_management_frame, @@ -151,24 +172,24 @@ class App: command=self.move_tab_right, bg=active_bg_color, fg=fg_color, - font=("Arial", 14), - padx=10, - pady=5 + font=("Arial", self.scale_font(14)), + padx=self.scale_size(10), + pady=self.scale_size(5) ) - move_right_btn.pack(side=tk.LEFT, padx=5) + move_right_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) # Profile dropdown - tk.Label(tab_management_frame, text="Profile:", bg=bg_color, fg=fg_color, font=("Arial", 14)).pack(side=tk.LEFT, padx=(20, 5)) + tk.Label(tab_management_frame, text="Profile:", bg=bg_color, fg=fg_color, font=("Arial", self.scale_font(14))).pack(side=tk.LEFT, padx=(self.scale_size(20), self.scale_size(5))) self.profile_var = tk.StringVar() self.profile_dropdown = ttk.Combobox( tab_management_frame, textvariable=self.profile_var, - font=("Arial", 14), - width=20, + font=("Arial", self.scale_font(14)), + width=self.scale_size(20), state="readonly" ) - self.profile_dropdown.pack(side=tk.LEFT, padx=5) + self.profile_dropdown.pack(side=tk.LEFT, padx=self.scale_size(5)) self.profile_dropdown.bind("<>", self.on_profile_selected) # New profile button @@ -178,11 +199,11 @@ class App: command=self.new_profile_dialog, bg=active_bg_color, fg=fg_color, - font=("Arial", 14), - padx=10, - pady=5 + font=("Arial", self.scale_font(14)), + padx=self.scale_size(10), + pady=self.scale_size(5) ) - new_profile_btn.pack(side=tk.LEFT, padx=5) + new_profile_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) # Save profile button save_profile_btn = tk.Button( @@ -191,11 +212,11 @@ class App: command=self.save_profile_dialog, bg=active_bg_color, fg=fg_color, - font=("Arial", 14), - padx=10, - pady=5 + font=("Arial", self.scale_font(14)), + padx=self.scale_size(10), + pady=self.scale_size(5) ) - save_profile_btn.pack(side=tk.LEFT, padx=5) + save_profile_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) # Load profiles self.profiles_dir = "profiles" @@ -647,8 +668,8 @@ class App: self.tabs[key] = tab def create_light_control_widgets(self, tab, tab_name, ids, initial_settings): - slider_length = 600 - slider_width = 50 + slider_length = self.scale_size(600) + slider_width = self.scale_size(50) # Get initial pattern and load pattern-specific settings initial_pattern = initial_settings.get("pattern", "on") @@ -666,9 +687,43 @@ class App: initial_r, initial_g, initial_b = color_utils.hex_to_rgb(initial_hex_color) + # Create a scrollable frame for the tab content + canvas = tk.Canvas(tab, bg=bg_color, highlightthickness=0) + scrollbar = tk.Scrollbar(tab, orient="vertical", command=canvas.yview) + scrollable_frame = tk.Frame(canvas, bg=bg_color) + + scrollable_frame.bind( + "", + lambda e: canvas.configure(scrollregion=canvas.bbox("all")) + ) + + canvas_window = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas.configure(yscrollcommand=scrollbar.set) + + # Make canvas window resize with canvas + def configure_scroll_region(event): + canvas.configure(scrollregion=canvas.bbox("all")) + # Update canvas window width to match canvas + canvas_width = event.width + canvas.itemconfig(canvas_window, width=canvas_width) + + canvas.bind('', configure_scroll_region) + + canvas.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # Bind mousewheel to canvas (cross-platform) + def _on_mousewheel(event): + if event.num == 4 or event.delta > 0: + canvas.yview_scroll(-1, "units") + elif event.num == 5 or event.delta < 0: + canvas.yview_scroll(1, "units") + canvas.bind_all("", _on_mousewheel) + canvas.bind_all("", _on_mousewheel) + canvas.bind_all("", _on_mousewheel) + # Main frame to hold everything within the tab - main_tab_frame = tk.Frame(tab, bg=bg_color) - main_tab_frame.pack(expand=True, fill="both", padx=10, pady=10) + main_tab_frame = scrollable_frame # Left panel container for sliders and n inputs left_panel_container = tk.Frame(main_tab_frame, bg=bg_color) @@ -750,8 +805,8 @@ class App: delay_slider.set(initial_slider_pos) # Create a custom label to show the actual delay value, positioned like the default Scale value - delay_value_label = tk.Label(delay_container, text=f"{initial_delay}", font=("Arial", 12), bg=bg_color, fg=fg_color, width=5, anchor="e") - delay_value_label.pack(side=tk.LEFT, padx=(0, 5)) + delay_value_label = tk.Label(delay_container, text=f"{initial_delay}", font=("Arial", self.scale_font(12)), bg=bg_color, fg=fg_color, width=self.scale_size(5), anchor="e") + delay_value_label.pack(side=tk.LEFT, padx=(0, self.scale_size(5))) # Store min/max delay in tab widget for later use tab.min_delay = min_delay @@ -784,10 +839,10 @@ class App: "from_": 0, "to": 255, "increment": 1, - "width": 12, + "width": self.scale_size(12), "bg": bg_color, "fg": fg_color, - "font": ("Arial", 24), + "font": ("Arial", self.scale_font(24)), "buttonbackground": active_bg_color, } @@ -798,8 +853,8 @@ class App: n_frame.grid(row=(i-1)//2, column=(i-1)%2, padx=10, pady=10) n_inputs[f"n{i}_frame"] = n_frame # Store frame reference for hiding/showing - n_label = tk.Label(n_frame, text=f"n{i}", font=("Arial", 20), bg=bg_color, fg=fg_color) - n_label.pack(pady=(0, 5)) + n_label = tk.Label(n_frame, text=f"n{i}", font=("Arial", self.scale_font(20)), bg=bg_color, fg=fg_color) + n_label.pack(pady=(0, self.scale_size(5))) n_inputs[f"n{i}_label"] = n_label # Store label reference # Create a frame for the input with arrows on both sides @@ -828,29 +883,29 @@ class App: left_arrow = tk.Button( input_container, text="−", - font=("Arial", 32, "bold"), + font=("Arial", self.scale_font(32), "bold"), bg=active_bg_color, fg=fg_color, relief=tk.FLAT, command=decrease_value, - width=3, - height=1, + width=self.scale_size(3), + height=self.scale_size(1), ) - left_arrow.pack(side=tk.LEFT, padx=2) + left_arrow.pack(side=tk.LEFT, padx=self.scale_size(2)) # Entry in the middle n_entry = tk.Entry( input_container, textvariable=n_var, - font=("Arial", 24), + font=("Arial", self.scale_font(24)), bg=bg_color, fg=fg_color, - width=8, + width=self.scale_size(8), justify=tk.CENTER, relief=tk.SUNKEN, bd=2, ) - n_entry.pack(side=tk.LEFT, padx=2, ipady=8) + n_entry.pack(side=tk.LEFT, padx=self.scale_size(2), ipady=self.scale_size(8)) n_entry.bind("", lambda event: self.schedule_update_n_params(tab)) n_entry.bind("", lambda event: self.schedule_update_n_params(tab, force_send=True)) @@ -864,15 +919,15 @@ class App: right_arrow = tk.Button( input_container, text="+", - font=("Arial", 32, "bold"), + font=("Arial", self.scale_font(32), "bold"), bg=active_bg_color, fg=fg_color, relief=tk.FLAT, command=increase_value, - width=3, - height=1, + width=self.scale_size(3), + height=self.scale_size(1), ) - right_arrow.pack(side=tk.LEFT, padx=2) + right_arrow.pack(side=tk.LEFT, padx=self.scale_size(2)) n_inputs[f"n{i}"] = n_entry n_inputs[f"n{i}_var"] = n_var # Store the variable for later updates @@ -913,15 +968,15 @@ class App: # 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) + tk.Label(ids_frame, text="Associated Names:", font=("Arial", self.scale_font(20)), bg=bg_color, fg=fg_color).pack(pady=self.scale_size(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_inner_frame, text=str(light_id), font=("Arial", 18), bg=bg_color, fg=fg_color).pack( - side=tk.LEFT, padx=5, pady=2 + tk.Label(ids_inner_frame, text=str(light_id), font=("Arial", self.scale_font(18)), bg=bg_color, fg=fg_color).pack( + side=tk.LEFT, padx=self.scale_size(5), pady=self.scale_size(2) ) # Pack labels horizontally # --- New Frame to hold Patterns and Color Palette side-by-side --- @@ -931,7 +986,7 @@ class App: # Patterns section patterns_frame = tk.Frame(patterns_and_palette_frame, bg=bg_color, bd=2, relief=tk.GROOVE) patterns_frame.pack(side=tk.LEFT, padx=10, pady=5, fill=tk.BOTH, expand=True) # Pack to the left - tk.Label(patterns_frame, text="Patterns:", font=("Arial", 20), bg=bg_color, fg=fg_color).pack(pady=10) + tk.Label(patterns_frame, text="Patterns:", font=("Arial", self.scale_font(20)), bg=bg_color, fg=fg_color).pack(pady=self.scale_size(10)) tab.pattern_buttons = {} patterns = list(self.patterns.keys()) @@ -942,9 +997,9 @@ class App: command=lambda p=pattern_name: self.send_pattern(tab_name, p), bg=active_bg_color, fg=fg_color, - font=("Arial", 18), - padx=15, - pady=5, + font=("Arial", self.scale_font(18)), + padx=self.scale_size(15), + pady=self.scale_size(5), relief=tk.FLAT, ) button.pack(pady=5, fill=tk.X) @@ -957,8 +1012,8 @@ class App: color_palette_editor_frame.pack(side=tk.LEFT, padx=10, pady=5, fill=tk.BOTH, expand=True) # Pack to the left tab.color_palette_editor_frame = color_palette_editor_frame # Store reference for update_ui_for_pattern - tk.Label(color_palette_editor_frame, text="Color Palette:", font=("Arial", 20), bg=bg_color, fg=fg_color).pack( - pady=10 + tk.Label(color_palette_editor_frame, text="Color Palette:", font=("Arial", self.scale_font(20)), bg=bg_color, fg=fg_color).pack( + pady=self.scale_size(10) ) # Frame to hold color swatches (will be dynamic) @@ -975,9 +1030,9 @@ class App: command=lambda t=tab: self.add_color_to_palette(t), bg=active_bg_color, fg=fg_color, - font=("Arial", 16), - padx=10, - pady=5, + font=("Arial", self.scale_font(16)), + padx=self.scale_size(10), + pady=self.scale_size(5), relief=tk.FLAT, ) add_color_button.pack(side=tk.LEFT, expand=True, padx=5) @@ -988,9 +1043,9 @@ class App: command=lambda t=tab: self.remove_selected_color_from_palette(t), bg=active_bg_color, fg=fg_color, - font=("Arial", 16), - padx=10, - pady=5, + font=("Arial", self.scale_font(16)), + padx=self.scale_size(10), + pady=self.scale_size(5), relief=tk.FLAT, ) remove_color_button.pack(side=tk.RIGHT, expand=True, padx=5) @@ -1011,9 +1066,9 @@ class App: for i, hex_color in enumerate(tab.colors_in_palette): swatch_frame = tk.Frame( - tab.color_swatches_container, bg=hex_color, width=100, height=50, bd=2, relief=tk.SOLID + tab.color_swatches_container, bg=hex_color, width=self.scale_size(100), height=self.scale_size(50), bd=2, relief=tk.SOLID ) - swatch_frame.pack(pady=3, padx=5, fill=tk.X) + swatch_frame.pack(pady=self.scale_size(3), padx=self.scale_size(5), fill=tk.X) # Bind click to select this color for editing swatch_frame.bind("", lambda event, idx=i, t=tab: self.select_color_in_palette(t, idx)) @@ -1023,9 +1078,9 @@ class App: text=f"Color {i+1}", bg=hex_color, fg=color_utils.get_contrast_text_color(hex_color), - font=("Arial", 14), - width=5, - height=3, + font=("Arial", self.scale_font(14)), + width=self.scale_size(5), + height=self.scale_size(3), ) swatch_label.pack(expand=True, fill=tk.BOTH) swatch_label.bind("", lambda event, idx=i, t=tab: self.select_color_in_palette(t, idx))