diff --git a/src/cmd_ui/command_box.py b/src/cmd_ui/command_box.py index 750f1bb..738bd39 100644 --- a/src/cmd_ui/command_box.py +++ b/src/cmd_ui/command_box.py @@ -6,12 +6,18 @@ from src.core.utils import const class CommandBox: + """ + Represents a command input box for user interactions within the + application. This class initializes the command box UI element and manages + its dimensions based on window size changes. + """ + def __init__(self, manager: pygame_gui.core.interfaces.IUIManagerInterface): - self.relative_rect = pg.Rect(0, 0, pygame_window.window_width - const.COMMAND_BOX_ANCHOR_OFFSET * 2, 50) - self.relative_rect.bottomleft = (const.COMMAND_BOX_ANCHOR_OFFSET, -const.COMMAND_BOX_ANCHOR_OFFSET) + relative_rect = pg.Rect(0, 0, pygame_window.window_width - const.COMMAND_BOX_ANCHOR_OFFSET * 2, 50) + relative_rect.bottomleft = (const.COMMAND_BOX_ANCHOR_OFFSET, -const.COMMAND_BOX_ANCHOR_OFFSET) - self.command_box = pygame_gui.elements.UITextEntryLine( - relative_rect=self.relative_rect, + self.UI = pygame_gui.elements.UITextEntryLine( + relative_rect=relative_rect, manager=manager, anchors={ "left": "left", @@ -21,7 +27,11 @@ def __init__(self, manager: pygame_gui.core.interfaces.IUIManagerInterface): object_id=const.COMMAND_BOX_OBJECT_ID ) - self.command_box.show() + self.UI.show() def on_window_size_changed(self): - self.command_box.set_dimensions((pygame_window.window_width - const.COMMAND_BOX_ANCHOR_OFFSET * 2, 50)) + """ + Updates the dimensions of the command box when the window size changes. + """ + + self.UI.set_dimensions((pygame_window.window_width - const.COMMAND_BOX_ANCHOR_OFFSET * 2, const.COMMAND_BOX_HEIGHT)) diff --git a/src/cmd_ui/ui_manager.py b/src/cmd_ui/ui_manager.py index 59e7b22..7c2b940 100644 --- a/src/cmd_ui/ui_manager.py +++ b/src/cmd_ui/ui_manager.py @@ -5,17 +5,48 @@ from src.core import pygame_window class UIManager: + """ + Manages the user interface elements of the application using pygame_gui. + This class handles the initialization, updating, event processing, and drawing + of UI components on the screen. + """ + def __init__(self): self.manager = pygame_gui.UIManager(pygame_window.size, const.CMD_THEME_FILE) def update(self, dt_time: float): + """ + Updates the UI manager with the elapsed time since the last frame. + This method ensures that all UI elements are refreshed and responsive to changes. + + Args: + dt_time (float): The time in seconds since the last update call. + """ + self.manager.update(dt_time) def process_event(self, event: pg.event.Event): + """ + Processes input events for the UI manager. + This method allows the user interface to respond to various user interactions, + such as mouse clicks and keyboard inputs. + """ + self.manager.process_events(event) def draw(self, screen: pg.Surface): + """ + Draws the UI elements onto the specified surface. + This method renders all visible components of the user interface. + """ + self.manager.draw_ui(screen) - + def on_window_size_changed(self): + """ + Adjusts the UI manager's resolution when the window size changes. + This method ensures that all UI elements are correctly scaled and positioned + according to the new dimensions of the window. + """ + self.manager.set_window_resolution(pygame_window.size) \ No newline at end of file diff --git a/src/core/dataclasses/theme.py b/src/core/dataclasses/theme.py index e5919ae..02bc4dd 100644 --- a/src/core/dataclasses/theme.py +++ b/src/core/dataclasses/theme.py @@ -5,6 +5,12 @@ @dataclass(slots=True, frozen=True) class Theme: + """ + Represents a theme configuration for the user interface. + This class encapsulates various color settings used throughout the application, + allowing for consistent styling and easy theme management. + """ + CMD_UI_FILE_PATH: Optional[str] NAME: str diff --git a/src/core/tree_utils/segment_tree.py b/src/core/tree_utils/segment_tree.py index 99f9d72..31b9810 100644 --- a/src/core/tree_utils/segment_tree.py +++ b/src/core/tree_utils/segment_tree.py @@ -20,6 +20,7 @@ def __init__(self, array: list[int], function_obj: Function): function_obj (Function): An object containing the function used for combining values and the value to return for invalid queries. """ + self.array = array self.root = Node() @@ -38,6 +39,7 @@ def array_length(self) -> int: Returns: int: The length of the array. """ + return len(self.array) def switch_function(self, function_obj: Function): @@ -69,6 +71,7 @@ def query(self, q_low: int, q_high: int) -> int: Returns: int: The result of the query for the specified range. """ + return self._query(q_low, q_high, self.root, 0, self.array_length-1) def update(self, pos: int, val: int) -> None: @@ -85,6 +88,7 @@ def update(self, pos: int, val: int) -> None: Returns: None """ + self.array[pos] = val self._update(pos, val, self.root, 0, self.array_length-1) @@ -95,6 +99,7 @@ def rebuild(self): the tree structure based on the current array. It ensures that the segment tree is updated to reflect any changes in the underlying data. """ + self.root = Node() self._build(self.root, 0, self.array_length-1) @@ -112,6 +117,7 @@ def _build(self, node: Node, low: int, high: int, ID: int = 1) -> None: high (int): The upper index of the range for the current node. ID (int, optional): The identifier for the current node. Defaults to 1. """ + if self.array_length == 0: return @@ -144,6 +150,7 @@ def _update(self, pos: int, val: int, node: Node, low: int, high: int) -> None: low (int): The lower index of the range for the current node. high (int): The upper index of the range for the current node. """ + if low == high: node.data = val return @@ -172,6 +179,7 @@ def _query(self, q_low: int, q_high: int, node: Node, low: int, high: int) -> in low (int): The lower index of the range for the current node. high (int): The upper index of the range for the current node. """ + if self._is_query_invalid(q_low, q_high, low, high): return self._INVALID_QUERY if self._is_query_within_range(q_low, q_high, low, high): @@ -200,6 +208,7 @@ def _is_query_invalid(self, q_low: int, q_high: int, low: int, high: int) -> boo Returns: bool: True if the query range is invalid, otherwise False. """ + return low > high or low > q_high or high < q_low def _is_query_within_range(self, q_low: int, q_high: int, low: int, high: int) -> bool: @@ -219,4 +228,5 @@ def _is_query_within_range(self, q_low: int, q_high: int, low: int, high: int) - Returns: bool: True if the current range is within the query range, otherwise False. """ + return q_low <= low and high <= q_high diff --git a/src/core/utils/app_enum.py b/src/core/utils/app_enum.py index cb37442..1ca23ce 100644 --- a/src/core/utils/app_enum.py +++ b/src/core/utils/app_enum.py @@ -11,6 +11,7 @@ class ContourEnum(Enum): LEFT (str): Represents the left side contour. RIGHT (str): Represents the right side contour. """ + LEFT = "left" RIGHT = "right" @@ -25,12 +26,19 @@ class VisibilityEnum(Enum): NODE_DATA_FIELD: Represents the visibility of the node data field. NODE_INFO_FIELD: Represents the visibility of the node information field. """ + ARRAY_FIELD = auto() NODE_DATA_FIELD = auto() NODE_INFO_FIELD = auto() @unique class JSONThemeFieldsEnum(Enum): + """ + Enumerates the fields used in JSON theme files that this app uses. + This class provides a set of constants that represent the various attributes + listed in a JSON theme file, facilitating safer data access. + """ + NAME = "Name" PALETTE = "Palette" USE_DEFAULT_CMD_UI = "use_default_cmd_ui" diff --git a/src/core/utils/constants.py b/src/core/utils/constants.py index c8468f4..259f629 100644 --- a/src/core/utils/constants.py +++ b/src/core/utils/constants.py @@ -43,6 +43,7 @@ # -- Command box COMMAND_BOX_OBJECT_ID = "#command-box" COMMAND_BOX_ANCHOR_OFFSET = 20 +COMMAND_BOX_HEIGHT = 50 # Links GITHUB_LINK: WebLink = WebLink("https://github.com/bennett-nguyen/KAY") diff --git a/src/core/window.py b/src/core/window.py index 0fda1ad..91a618d 100644 --- a/src/core/window.py +++ b/src/core/window.py @@ -19,6 +19,12 @@ ) class PygameWindow: + """ + Represents a window for the Pygame application, managing the display and + rendering settings. This class initializes the window, sets the icon and caption, + and provides methods to manipulate the window's appearance and frame rate. + """ + __slots__ = ("screen", "clock") def __init__(self): info_obj = pg.display.Info() @@ -34,29 +40,82 @@ def __init__(self): pg.display.set_icon(icon) def fill_background(self, color: pg.Color): + """ + Fills the window's background with the specified color. + This method is used to clear the screen before drawing new graphics, ensuring + that the previous frame does not interfere with the current one. + + Args: + color (pg.Color): The color to fill the background with. + """ + self.screen.fill(color) def set_framerate(self, FPS: float) -> float: + """ + Sets the frame rate for the application and regulates the speed of the game loop. + + Args: + FPS (float): The desired frames per second for the application. + """ + return self.clock.tick(FPS) @property def size(self) -> tuple[int, int]: + """ + Retrieves the current size of the window. + This property returns the width and height of the window as a tuple, allowing + other components of the application to adapt to the window's dimensions. + + Returns: + tuple[int, int]: A tuple containing the width and height of the window. + """ + return self.screen.get_size() @property def window_width(self) -> int: + """ + Retrieves the current width of the window. + + Returns: + int: The width of the window in pixels. + """ + return self.screen.get_width() @property def window_height(self) -> int: + """ + Retrieves the current height of the window. + + Returns: + int: The height of the window in pixels. + """ + return self.screen.get_height() @property def half_window_width(self) -> int: + """ + Retrieves half the current width of the window. + + Returns: + int: Half the width of the window in pixels. + """ + return int(self.window_width / 2) @property def half_window_height(self) -> int: + """ + Retrieves half the current height of the window. + + Returns: + int: Half the height of the window in pixels. + """ + return int(self.window_height / 2) pygame_window = PygameWindow() \ No newline at end of file diff --git a/src/mvc/controller.py b/src/mvc/controller.py index a336465..89a43d0 100644 --- a/src/mvc/controller.py +++ b/src/mvc/controller.py @@ -27,10 +27,11 @@ def process_input(self, events: list[pg.event.Event]): Args: events (list[pg.event.Event]): A list of Pygame event objects to be processed. """ + cmdline_interface = self.model.cmdline_interface for event in events: - if event.type == pg.MOUSEWHEEL and not cmdline_interface.command_box.command_box.is_focused: + if event.type == pg.MOUSEWHEEL and not cmdline_interface.command_box.UI.is_focused: self.model.zoom(event.y) if event.type == pg.VIDEORESIZE: @@ -38,7 +39,7 @@ def process_input(self, events: list[pg.event.Event]): cmdline_interface.process_event(event) - if not cmdline_interface.command_box.command_box.is_focused: + if not cmdline_interface.command_box.UI.is_focused: self.model.pan() @@ -67,4 +68,4 @@ def update_view(self, dt_time: float): self.view.view_array(tree_manager.segment_tree.array, hovered_node) self.view.view_hovered_node_info(hovered_node) - cmdline_interface.draw_ui(pygame_window.screen) \ No newline at end of file + cmdline_interface.draw_ui() \ No newline at end of file diff --git a/src/mvc/model.py b/src/mvc/model.py index b14ed48..e0961de 100644 --- a/src/mvc/model.py +++ b/src/mvc/model.py @@ -6,12 +6,6 @@ class Model: - """ - Manages the application's data and interactions, including theme management and - tree structure handling. This class initializes the necessary components for - rendering and user interaction, providing methods for zooming and panning the view. - """ - def __init__(self): """ Initializes the model with the necessary components for managing themes and @@ -89,6 +83,12 @@ def pan(self): self.tree_manager.move_tree_by_delta_pos(delta_x, delta_y) self.previous_mouse_pos = self.current_mouse_pos self.tree_manager.compute_transformed_coordinates(self.zoom_level) - + def on_window_size_changed(self): + """ + Updates the command line interface when the window size changes. + This method ensures that the command line interface adapts to the new dimensions + of the window, maintaining a consistent user experience. + """ + self.cmdline_interface.on_window_size_changed() \ No newline at end of file diff --git a/src/mvc/model_helpers/command_line_interface.py b/src/mvc/model_helpers/command_line_interface.py index 01c2e80..e6232fb 100644 --- a/src/mvc/model_helpers/command_line_interface.py +++ b/src/mvc/model_helpers/command_line_interface.py @@ -3,21 +3,38 @@ import pygame as pg import pygame_gui +from src.core import pygame_window from src.core.utils import const from src.core.dataclasses import Theme from src.cmd_ui import CommandBox, UIManager class CMDLineInterface: + """ + Manages the command line interface for user input within the application. + This class handles event processing, UI updates, and theme management, + acting as a front-end for command inputs. + """ + def __init__(self): self.ui_manager = UIManager() self.command_box = CommandBox(self.ui_manager.manager) self._focused_textbox = False def process_event(self, event: pg.event.Event): + """ + Processes input events for the command line interface. + This method handles keyboard events to manage the focus state of the command box + and processes text entry completion, allowing for user interaction with the interface. + + Args: + event (pg.event.Event): The event to be processed, which may include keyboard + inputs and UI interactions. + """ + self.ui_manager.process_event(event) if self._focused_textbox: - self.command_box.command_box.focus() + self.command_box.UI.focus() self._focused_textbox = False if event.type == pg.KEYDOWN: @@ -25,20 +42,41 @@ def process_event(self, event: pg.event.Event): self._focused_textbox = True elif event.key == pg.K_ESCAPE: - self.command_box.command_box.unfocus() + self.command_box.UI.unfocus() elif event.type == pygame_gui.UI_TEXT_ENTRY_FINISHED \ and event.ui_object_id == const.COMMAND_BOX_OBJECT_ID and event.text: - self.command_box.command_box.clear() - self.command_box.command_box.focus() + self.command_box.UI.clear() + self.command_box.UI.focus() def update(self, dt_time: float): + """ + Updates the user interface manager with the elapsed time since the last frame. + This method ensures that all UI elements are refreshed and remain responsive to user interactions. + + Args: + dt_time (float): The time in seconds since the last update call. + """ + self.ui_manager.update(dt_time) - def draw_ui(self, screen: pg.Surface): - self.ui_manager.draw(screen) + def draw_ui(self): + """ + Draws the user interface elements onto the main application screen. + This method utilizes the UI manager to render all of its visible components. + """ + + self.ui_manager.draw(pygame_window.screen) def set_theme(self, theme: Theme): + """ + Sets the theme for the command line interface based on the provided theme object. + This method loads the theme configuration from a specified file or defaults to a + predefined theme if no file path is provided, and then saves the theme settings. + The UI manager will read the theme settings and change the theme of the command + line interface. + """ + if theme.CMD_UI_FILE_PATH is None: json_obj = json.loads(const.DEFAULT_CMD_THEME) else: @@ -49,5 +87,11 @@ def set_theme(self, theme: Theme): json.dump(json_obj, indent=4, fp=cmd_ui_file) def on_window_size_changed(self): + """ + Adjusts the user interface elements when the window size changes. + This method ensures that both the UI manager and the command box are updated + to reflect the new dimensions of the window. + """ + self.ui_manager.on_window_size_changed() self.command_box.on_window_size_changed() diff --git a/src/mvc/model_helpers/theme_manager.py b/src/mvc/model_helpers/theme_manager.py index 3df2356..fcb11f5 100644 --- a/src/mvc/model_helpers/theme_manager.py +++ b/src/mvc/model_helpers/theme_manager.py @@ -58,6 +58,7 @@ def create_theme(self, json_obj: dict[str, Any], file_name: str) -> Theme: Raises: KeyError: If the required keys are not present in the JSON object. """ + palette_obj = json_obj[JSONThemeFieldsEnum.PALETTE.value] cmd_ui_file_path = None @@ -89,6 +90,7 @@ def set_theme(self, name: str): Raises: KeyError: If the specified theme name does not exist in the themes dictionary. """ + try: self.current_theme = self.themes[name] except KeyError: diff --git a/src/mvc/model_helpers/tree_manager.py b/src/mvc/model_helpers/tree_manager.py index 479c095..6ab7e6f 100644 --- a/src/mvc/model_helpers/tree_manager.py +++ b/src/mvc/model_helpers/tree_manager.py @@ -12,6 +12,7 @@ class TreeManager: provides methods to switch functions, move the tree, and compute coordinates for rendering. """ + def __init__(self, data: list[int]): """ Initializes the TreeManager with the provided data and sets up the available @@ -37,6 +38,7 @@ def generate_node_position(self, zoom_level: float): preliminary values. It ensures that the nodes are positioned correctly for rendering in a visual representation of the tree. """ + self._compute_prelim_x(self.segment_tree.root) self._compute_final_coordinates(self.segment_tree.root, 0) self.compute_transformed_coordinates(zoom_level) @@ -49,6 +51,7 @@ def switch_function(self, name: str): Args: name (str): The name of the function to switch to. """ + self.current_function = self.available_functions[name] self.segment_tree.switch_function(self.current_function) @@ -64,6 +67,7 @@ def load_functions(self, exported_functions: list[Function]): Returns: None """ + for function in exported_functions: if function in self.available_functions: print(f"Function <{function.name}> already existed! Skipping...") @@ -82,6 +86,7 @@ def move_tree_by_delta_pos(self, delta_x: int, delta_y: int): delta_x (int): The change in the x-coordinate. delta_y (int): The change in the y-coordinate. """ + queue: deque[Node] = deque([self.segment_tree.root]) while queue: @@ -109,6 +114,7 @@ def compute_transformed_coordinates(self, zoom_level: float): Returns: None """ + queue: deque[Node] = deque([self.segment_tree.root]) while queue: @@ -134,6 +140,7 @@ def _compute_final_coordinates(self, node: Node, mod_sum: float): node (Node): The node for which to compute the final coordinates. mod_sum (float): The cumulative modifier to adjust the x-coordinate. """ + node.preliminary_x += mod_sum mod_sum += node.modifier node.modifier = 0 @@ -158,6 +165,7 @@ def _compute_prelim_x(self, node: Node): Args: node (Node): The node for which to compute the preliminary x-coordinate. """ + if node.is_leaf(): if node.is_left_node(): node.preliminary_x = 0 @@ -195,6 +203,7 @@ def _check_for_conflicts(self, node: Node): Args: node (Node): The node to check for positioning conflicts. """ + min_distance: float = const.TREE_DISTANCE + const.NODE_DISTANCE shift_value: float = 0.0