From 6a762d704aa8a036b8528b5c5b9a4811606d2d19 Mon Sep 17 00:00:00 2001 From: slomar1 Date: Tue, 3 Jun 2025 12:56:25 -0400 Subject: [PATCH] Add Dark Mode toggle (fixes #206) --- src/main.py | 188 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 142 insertions(+), 46 deletions(-) diff --git a/src/main.py b/src/main.py index 3a362de..ccb9da6 100644 --- a/src/main.py +++ b/src/main.py @@ -1,3 +1,5 @@ +# main.py + import pygame from display import Window, TextBox, SlideBox, DropdownBox, ButtonBox from algs import algorithmsDict @@ -5,56 +7,112 @@ import time import math -# Initialize pygame modules +# Initialize Pygame and set up a font pygame.init() - -# Font baseFont = pygame.font.SysFont('Arial', 24) -# Colors -grey = (100, 100, 100) -green = (125, 240, 125) -white = (250, 250, 250) -red = (255, 50, 50) -black = (0, 0, 0) -blue = (50, 50, 255) +# Light-mode colors +LIGHT_GREY = (100, 100, 100) +LIGHT_GREEN = (125, 240, 125) +LIGHT_WHITE = (250, 250, 250) +LIGHT_RED = (255, 50, 50) +LIGHT_BLACK = (0, 0, 0) +LIGHT_BLUE = (50, 50, 255) + +# Dark-mode colors +DARK_GREY = (50, 50, 50) +DARK_GREEN = (80, 180, 80) +DARK_WHITE = (30, 30, 30) # Nearly black, used as background +DARK_RED = (200, 30, 30) +DARK_BLACK = (230, 230, 230) # Light gray for text/borders +DARK_BLUE = (30, 30, 200) + +# Flag to keep track of current theme: False = Light, True = Dark +dark_mode = False + +# Window size constants +WINDOW_WIDTH = 900 +WINDOW_HEIGHT = 500 pygame.display.set_caption('Sorting Algorithms Visualizer') -screen = pygame.display.set_mode((900, 500)) +screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) window = Window(screen) + +# Add the UI widgets (size input box, delay slider, algorithm dropdown, play/stop button) window.add_widget( widget_id = 'size_input', - widget = TextBox((30, 440, 100, 50), 'Size', grey, baseFont, '100') + widget = TextBox((30, 440, 100, 50), 'Size', LIGHT_GREY, baseFont, '100') ) window.add_widget( - widget_id='delay_slider', - widget=SlideBox((140, 440, 150, 50), 'Delay', grey, baseFont) + widget_id = 'delay_slider', + widget = SlideBox((140, 440, 150, 50), 'Delay', LIGHT_GREY, baseFont) ) window.add_widget( widget_id = 'algorithm_input', - widget = DropdownBox((300, 440, 200, 50), 'Algorithm', grey, baseFont, list(algorithmsDict.keys()), white) + widget = DropdownBox( + (300, 440, 200, 50), + 'Algorithm', + LIGHT_GREY, + baseFont, + list(algorithmsDict.keys()), + LIGHT_WHITE + ) ) window.add_widget( widget_id = 'play_button', widget = ButtonBox((510, 445, 40, 40), 'res/playButton.png', 'res/stopButton.png') ) +# drawBars: draws the sorting bars on screen, using whichever +# color-palette is currently active def drawBars(screen, array, redBar1, redBar2, blueBar1, blueBar2, greenRows = {}): - '''Draw the bars and control their colors''' numBars = len(array) if numBars != 0: - bar_width = 900 / numBars + bar_width = WINDOW_WIDTH / numBars ceil_width = math.ceil(bar_width) - - for num in range(numBars): - if num in (redBar1, redBar2) : color = red - elif num in (blueBar1, blueBar2): color = blue - elif num in greenRows : color = green - else : color = grey - pygame.draw.rect(screen, color, (num * bar_width, 400 - array[num], ceil_width, array[num])) - + else: + return # nothing to draw if array is empty + + # Choose the right set of colors based on dark_mode + if dark_mode: + grey_color = DARK_GREY + green_color = DARK_GREEN + red_color = DARK_RED + blue_color = DARK_BLUE + else: + grey_color = LIGHT_GREY + green_color = LIGHT_GREEN + red_color = LIGHT_RED + blue_color = LIGHT_BLUE + + # Loop over each bar index and draw it with the correct color + for idx in range(numBars): + if idx in (redBar1, redBar2): + color = red_color + elif idx in (blueBar1, blueBar2): + color = blue_color + elif idx in greenRows: + color = green_color + else: + color = grey_color + + pygame.draw.rect( + screen, + color, + ( + idx * bar_width, + 400 - array[idx], + ceil_width, + array[idx] + ) + ) + +# main loop: handles events, toggling dark_mode, starting/stopping sorting, +# drawing the bars, and rendering widgets each frame. def main(): + global dark_mode # so we can flip it on mouse clicks + numbers = [] running = True isPlaying = False @@ -62,51 +120,89 @@ def main(): sortingIterator = None last_iteration = 0 + # A small clickable rectangle in the top-right to toggle Light/Dark + toggle_rect = pygame.Rect(WINDOW_WIDTH - 170, 10, 150, 30) + while running: - screen.fill(white) + # Fill background each frame using the correct mode + if dark_mode: + screen.fill(DARK_WHITE) + else: + screen.fill(LIGHT_WHITE) + for event in pygame.event.get(): if event.type == pygame.QUIT: running = False + # If user left-clicks in our toggle_rect, flip dark_mode + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + if toggle_rect.collidepoint(event.pos): + dark_mode = not dark_mode + # (Optional) If you want the widget colors to switch, you'd do it here. + + # Let the Window/UI system handle the event (text input, button press, etc.) window.update(event) - # Get delay in seconds + # Get the delay value (slider output is 0.0 to 1.0) and divide by 10 delay = window.get_widget_value('delay_slider') / 10 - isPlaying = window.get_widget_value('play_button') + + # If “Play” is pressed and we aren’t already sorting, start a new random array if isPlaying and not isSorting: - # Random list to be sorted numBars = int(window.get_widget_value('size_input')) - numbers = [randint(10, 400) for i in range(numBars)] - - # Initialize sorting iterator + numbers = [randint(10, 400) for _ in range(numBars)] sortingAlgorithm = window.get_widget_value('algorithm_input') sortingIterator = algorithmsDict[sortingAlgorithm](numbers, 0, numBars - 1) isSorting = True + # If Play is off, make sure isSorting is False if not isPlaying: isSorting = False if isSorting: - try: - # Get the next state from the sorting iterator - if time.time() - last_iteration >= delay: - numbers, redBar1, redBar2, blueBar1, blueBar2 = next(sortingIterator) - last_iteration = time.time() - - drawBars(screen, numbers, redBar1, redBar2, blueBar1, blueBar2) - window.render() - pygame.display.update() - - except StopIteration: - isSorting = False - window.set_widget_value('play_button', False) + try: + # Advance the sorting iterator once per 'delay' seconds + if time.time() - last_iteration >= delay: + numbers, redBar1, redBar2, blueBar1, blueBar2 = next(sortingIterator) + last_iteration = time.time() + + drawBars(screen, numbers, redBar1, redBar2, blueBar1, blueBar2) + window.render() + pygame.display.update() + + except StopIteration: + # Sorting is done + isSorting = False + window.set_widget_value('play_button', False) + else: + # If we're not actively sorting, draw bars as fully sorted (all green) drawBars(screen, numbers, -1, -1, -1, -1, greenRows=set(range(len(numbers)))) + # Draw the Dark/Light Mode toggle box and its label each frame + if dark_mode: + rect_fill = DARK_GREY + rect_border = DARK_BLACK + text_color = DARK_BLACK + label = "Light Mode" + else: + rect_fill = LIGHT_GREY + rect_border = LIGHT_BLACK + text_color = LIGHT_BLACK + label = "Dark Mode" + + # Draw a filled rectangle and a 2px border for the toggle + pygame.draw.rect(screen, rect_fill, toggle_rect) + pygame.draw.rect(screen, rect_border, toggle_rect, 2) + + # Center the label text in that rectangle + label_surf = baseFont.render(label, True, text_color) + label_rect = label_surf.get_rect(center=toggle_rect.center) + screen.blit(label_surf, label_rect.topleft) + + # Finally, render all widgets again (so they appear on top) window.render() pygame.display.update() - if __name__ == '__main__': main()