diff --git a/multiplyer-racing/main.py b/multiplyer-racing/main.py index e69de29..c1c6041 100644 --- a/multiplyer-racing/main.py +++ b/multiplyer-racing/main.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python3 +""" +A single-player pygame racing simulation where engine RPM and speed (in mph) +are modeled as independent—but coupled—systems. The engine’s RPM (tachometer) +is updated by throttle/brake input plus a damping effect that tends to “lock” +the engine to the value expected from the current vehicle speed and gear. +Vehicle acceleration is computed from the RPM difference (engine load) +minus drag, yielding a more realistic feel. + +Controls: + - LEFT/RIGHT arrows: steer + - UP arrow: throttle (increases engine RPM and, via the dynamics, accelerates the car) + - DOWN arrow: brake + - E: upshift (gear +1 with rev-match drop) + - Q: downshift (gear -1 with a slight rev bump) + - R: toggle reverse (gear 0) versus forward (gear 1) +""" + +import sys +import math +import random +import pygame + +# ----- Screen & Road Configuration ----- +SCREEN_WIDTH = 800 +SCREEN_HEIGHT = 600 +ROAD_WIDTH = 150 # Road drawn as a wide filled polygon +FPS = 60 + +# ----- Engine & Vehicle Dynamics ----- +IDLE_RPM = 800 +REDLINE_RPM = 6000 + +# Engine dynamics (in rpm/sec) +THROTTLE_RPM_RATE = 800 # When throttle is pressed, engine spins up +BRAKE_RPM_RATE = 2500 # When braking, engine RPM drops rapidly +DAMPING_FACTOR = 5 # How strongly engine RPM is forced toward the "ideal" RPM + +# These dynamics model the connection between engine torque and vehicle acceleration: +# The vehicle's acceleration (in mph/s) is proportional to the difference between the +# current engine RPM and the ideal RPM that would be produced by the current speed in the given gear. +ACCELERATION_CONVERSION = 150 # (rpm difference) to mph/s conversion factor +DRAG_COEFF = 0.02 # Drag (per second) that always slows the vehicle + +# Conversion constant (empirical) for what engine RPM should be for a given vehicle speed +# in a particular gear. In this model, ideal_rpm = speed × gear_ratio × K. +K = 21 + +# Maximum vehicle speed (in mph) +MAX_SPEED = 90 + +# ----- Transmission & Gear Ratios ----- +# These gear ratios (including reverse = gear 0) are assumed to incorporate the effect +# of the transmission (and final drive) so that: +# ideal_rpm = speed (mph) × gear_ratio × K. +GEAR_RATIOS = { + 0: 4.866, # Reverse + 1: 4.696, + 2: 2.985, + 3: 2.146, + 4: 1.769, + 5: 1.520, + 6: 1.275, + 7: 1.000, + 8: 0.854, + 9: 0.689, + 10: 0.636 +} + +# Turning rate (radians per second) +TURN_RATE = 2.0 + +# ----- Gauge Configuration ----- +GAUGE_RADIUS = 50 +SPEED_GAUGE_CENTER = (150, 100) +TACH_GAUGE_CENTER = (650, 100) + +# ----- Helper Functions ----- +def rotate_point(point, angle): + """Rotate a 2D point by angle in radians.""" + x, y = point + cos_a = math.cos(angle) + sin_a = math.sin(angle) + return (x * cos_a - y * sin_a, x * sin_a + y * cos_a) + +def draw_gauge(surface, center, radius, min_val, max_val, current_val, label): + """ + Draw an analog gauge (circle with a needle) mapping a value from min_val to max_val + to an angle between -135° and +135°. + """ + pygame.draw.circle(surface, (255, 255, 255), center, radius, 2) + min_angle = -135 + max_angle = 135 + ratio = (current_val - min_val) / (max_val - min_val) if max_val != min_val else 0 + angle_deg = min_angle + ratio * (max_angle - min_angle) + angle_rad = math.radians(angle_deg) + needle_length = radius - 10 + needle_end = (center[0] + needle_length * math.cos(angle_rad), + center[1] + needle_length * math.sin(angle_rad)) + pygame.draw.line(surface, (255, 0, 0), center, needle_end, 3) + font = pygame.font.SysFont(None, 24) + text_surf = font.render(f"{label}: {int(current_val)}", True, (255, 255, 255)) + text_rect = text_surf.get_rect(center=(center[0], center[1] + radius + 20)) + surface.blit(text_surf, text_rect) + +def point_line_distance(P, A, B): + """Return the shortest distance from point P to line segment AB.""" + AB = (B[0] - A[0], B[1] - A[1]) + AP = (P[0] - A[0], P[1] - A[1]) + ab2 = AB[0]**2 + AB[1]**2 + if ab2 == 0: + return math.hypot(P[0] - A[0], P[1] - A[1]) + t = max(0, min(1, (AP[0]*AB[0] + AP[1]*AB[1]) / ab2)) + proj = (A[0] + t*AB[0], A[1] + t*AB[1]) + return math.hypot(P[0] - proj[0], P[1] - proj[1]) + +# ----- Game Objects ----- +class Car: + def __init__(self): + self.x = 0.0 # Position (in feet or arbitrary units that scale to mph) + self.y = 0.0 + self.angle = 0.0 # Orientation (radians; 0 means facing right) + self.engine_rpm = IDLE_RPM + self.gear = 1 # 0 = reverse, 1..10 forward + self.speed = 0.0 # Vehicle speed in mph + + def update(self, throttle, brake, dt): + """ + Update engine RPM and vehicle speed over time. + - The engine RPM is updated based on throttle input, brake input, and a damping term that + forces it toward the "ideal" RPM determined by the current speed. + - The vehicle acceleration is then computed from the difference between actual and ideal RPM. + """ + # Compute the ideal engine RPM given the current speed and gear. + # For realism, if the car is stopped, ideal RPM should be at least IDLE_RPM. + ideal_rpm = max(IDLE_RPM, self.speed * GEAR_RATIOS[self.gear] * K) + + # --- Engine RPM Dynamics --- + # Throttle increases engine RPM; braking drops it. + throttle_effect = THROTTLE_RPM_RATE if throttle else 0 + brake_effect = BRAKE_RPM_RATE if brake else 0 + # Engine tends to return toward the ideal RPM. + d_rpm = throttle_effect - brake_effect - DAMPING_FACTOR * (self.engine_rpm - ideal_rpm) + self.engine_rpm += d_rpm * dt + # Clamp engine RPM between IDLE and REDLINE. + if self.engine_rpm < IDLE_RPM: + self.engine_rpm = IDLE_RPM + if self.engine_rpm > REDLINE_RPM: + self.engine_rpm = REDLINE_RPM + + # --- Vehicle Acceleration Dynamics --- + # The acceleration is proportional to how much the engine is "over-revving" relative to ideal. + # A positive difference means extra torque; a negative difference means the engine is not producing enough torque. + accel = (self.engine_rpm - ideal_rpm) / ACCELERATION_CONVERSION + # Subtract drag proportional to speed. + accel -= DRAG_COEFF * self.speed + # Update vehicle speed. + self.speed += accel * dt + if self.speed < 0: + self.speed = 0 + if self.speed > MAX_SPEED: + self.speed = MAX_SPEED + + # --- Update Position --- + # When in reverse (gear 0), the vehicle moves backward. + direction = 1 if self.gear != 0 else -1 + # Convert speed (mph) to distance per second. (1 mph ≈ 1.46667 ft/s) + ft_per_sec = self.speed * 1.46667 + self.x += math.cos(self.angle) * ft_per_sec * dt * direction + self.y += math.sin(self.angle) * ft_per_sec * dt * direction + + def rev_match_on_shift_up(self): + """Simulate a realistic drop in RPM when upshifting (rev-match).""" + # On an upshift, the engine RPM typically drops by 10–20%. + self.engine_rpm *= 0.85 + if self.engine_rpm < IDLE_RPM: + self.engine_rpm = IDLE_RPM + + def rev_match_on_shift_down(self): + """Simulate a realistic bump in RPM when downshifting.""" + self.engine_rpm *= 1.15 + if self.engine_rpm > REDLINE_RPM: + self.engine_rpm = REDLINE_RPM + +class Track: + """ + A procedural track that extends as the car moves, giving the illusion of an infinite road. + """ + def __init__(self): + self.segments = [] + self.last_point = (0.0, 0.0) + self.last_angle = 0.0 + self.generate_initial_track() + + def generate_initial_track(self): + num_segments = 20 + for _ in range(num_segments): + self.extend_segment() + + def extend_segment(self): + length = 150 + angle_change = random.uniform(-0.3, 0.3) + self.last_angle += angle_change + dx = length * math.cos(self.last_angle) + dy = length * math.sin(self.last_angle) + next_point = (self.last_point[0] + dx, self.last_point[1] + dy) + self.segments.append((self.last_point, next_point)) + self.last_point = next_point + + def ensure_track_length(self, car_pos, min_distance=500): + dist = math.hypot(self.last_point[0] - car_pos[0], self.last_point[1] - car_pos[1]) + while dist < min_distance: + self.extend_segment() + dist = math.hypot(self.last_point[0] - car_pos[0], self.last_point[1] - car_pos[1]) + +def is_on_road(car, track, road_width): + """ + Checks whether the car's center is within half the road width of any track segment. + """ + P = (car.x, car.y) + hw = road_width / 2 + for seg in track.segments: + A, B = seg + if point_line_distance(P, A, B) <= hw: + return True + return False + +# ----- Drawing Functions ----- +def draw_road(surface, track, offset_x, offset_y, road_width): + """ + Draw each track segment as a filled polygon representing a wide road. + """ + for seg in track.segments: + start, end = seg + dx = end[0] - start[0] + dy = end[1] - start[1] + seg_len = math.hypot(dx, dy) + if seg_len == 0: + continue + # Compute a perpendicular vector. + nx = -dy / seg_len + ny = dx / seg_len + half_w = road_width / 2 + start_left = (start[0] + nx * half_w, start[1] + ny * half_w) + start_right = (start[0] - nx * half_w, start[1] - ny * half_w) + end_left = (end[0] + nx * half_w, end[1] + ny * half_w) + end_right = (end[0] - nx * half_w, end[1] - ny * half_w) + poly = [ + (int(start_left[0] + offset_x), int(start_left[1] + offset_y)), + (int(end_left[0] + offset_x), int(end_left[1] + offset_y)), + (int(end_right[0] + offset_x), int(end_right[1] + offset_y)), + (int(start_right[0] + offset_x), int(start_right[1] + offset_y)) + ] + pygame.draw.polygon(surface, (100, 100, 100), poly) + # Draw a center line for visual effect. + center_start = (start[0] + offset_x, start[1] + offset_y) + center_end = (end[0] + offset_x, end[1] + offset_y) + pygame.draw.line(surface, (255, 255, 255), center_start, center_end, 2) + +def draw_car(surface, car, offset_x, offset_y): + """ + Draw the car as a rotated, car-like polygon. + """ + center = (car.x + offset_x, car.y + offset_y) + half_length = 40 / 2 + half_width = 20 / 2 + # Define a 5-point polygon for the car shape. + points = [ + ( half_length, 0), # Front tip + ( half_length * 0.2, -half_width), # Rear top + (-half_length, -half_width), # Rear left + (-half_length, half_width), # Rear right + ( half_length * 0.2, half_width) # Rear bottom + ] + rotated = [] + for p in points: + rp = rotate_point(p, car.angle) + rotated.append((rp[0] + center[0], rp[1] + center[1])) + pygame.draw.polygon(surface, (255, 0, 0), rotated) + pygame.draw.polygon(surface, (255, 255, 255), rotated, 2) + +# ----- Main Game Loop ----- +def main(): + pygame.init() + screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) + pygame.display.set_caption("Super Realistic Car Simulation") + clock = pygame.time.Clock() + font = pygame.font.SysFont(None, 24) + + car = Car() + track = Track() + + running = True + while running: + dt = clock.tick(FPS) / 1000.0 + + # ----- Event Handling ----- + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + # Upshift (E) + if event.key == pygame.K_e and car.gear < 10: + old_ratio = GEAR_RATIOS[car.gear] + car.gear += 1 + # Rev-match on upshift: drop RPM to roughly maintain consistency. + car.rev_match_on_shift_up() + # Downshift (Q), but do not downshift below gear 1. + elif event.key == pygame.K_q and car.gear > 1: + old_ratio = GEAR_RATIOS[car.gear] + car.gear -= 1 + car.rev_match_on_shift_down() + # Toggle reverse (R) + elif event.key == pygame.K_r: + if car.gear == 0: + car.gear = 1 + else: + car.gear = 0 + + # ----- Continuous Input ----- + keys = pygame.key.get_pressed() + if keys[pygame.K_LEFT]: + car.angle -= TURN_RATE * dt + if keys[pygame.K_RIGHT]: + car.angle += TURN_RATE * dt + throttle = keys[pygame.K_UP] + brake = keys[pygame.K_DOWN] + + # ----- Update Car & Extend Track ----- + car.update(throttle, brake, dt) + track.ensure_track_length((car.x, car.y), min_distance=500) + + # Determine the view offset so the car is centered. + offset_x = SCREEN_WIDTH / 2 - car.x + offset_y = SCREEN_HEIGHT / 2 - car.y + + # ----- Drawing ----- + screen.fill((0, 150, 0)) # Grass background. + draw_road(screen, track, offset_x, offset_y, ROAD_WIDTH) + draw_car(screen, car, offset_x, offset_y) + + # Display status (on-road/off-road) + status_text = "On Road" if is_on_road(car, track, ROAD_WIDTH) else "Off Road" + status_surf = font.render(status_text, True, (255, 255, 255)) + screen.blit(status_surf, (10, 10)) + + # Draw gauges. + # Speed gauge shows vehicle speed in mph. + draw_gauge(screen, SPEED_GAUGE_CENTER, GAUGE_RADIUS, 0, MAX_SPEED, car.speed, "Speed (mph)") + # Tachometer shows engine RPM. + draw_gauge(screen, TACH_GAUGE_CENTER, GAUGE_RADIUS, 0, REDLINE_RPM, car.engine_rpm, "RPM") + + # Display current gear. + gear_text = "Reverse" if (car.gear == 0) else f"Gear: {car.gear}" + gear_surf = font.render(gear_text, True, (255, 255, 255)) + screen.blit(gear_surf, (SPEED_GAUGE_CENTER[0] - GAUGE_RADIUS, SPEED_GAUGE_CENTER[1] + GAUGE_RADIUS + 40)) + + pygame.display.flip() + + pygame.quit() + sys.exit() + +if __name__ == "__main__": + main() diff --git a/pygame-imgui-v2/__pycache__/imgui.cpython-311.pyc b/pygame-imgui-v2/__pycache__/imgui.cpython-311.pyc new file mode 100644 index 0000000..b4c55fd Binary files /dev/null and b/pygame-imgui-v2/__pycache__/imgui.cpython-311.pyc differ diff --git a/pygame-imgui-v2/example.py b/pygame-imgui-v2/example.py new file mode 100644 index 0000000..3fe797d --- /dev/null +++ b/pygame-imgui-v2/example.py @@ -0,0 +1,72 @@ +import pygame +import sys +from imgui import PyGUI + +WIDTH, HEIGHT = 800, 600 + +class Ball: + def __init__(self): + self.pos = [WIDTH // 2, HEIGHT // 2] + self.speed = 5 + self.direction = 45 + self.radius = 20 + self.color = (255, 0, 0) + + def update(self): + # (For demonstration, implement ball movement if desired.) + pass + + def draw(self, surface): + pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius) + +def main(): + pygame.init() + screen = pygame.display.set_mode((WIDTH, HEIGHT)) + pygame.display.set_caption("PyGUI Example") + clock = pygame.time.Clock() + PyGUI.init(screen) + + ball = Ball() + ball_speed = ball.speed + ball_direction = ball.direction + ball_radius = ball.radius + ball_color_r, ball_color_g, ball_color_b = ball.color + bounce_enabled = False + + while True: + events = pygame.event.get() + for event in events: + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + PyGUI.Update(events) + + screen.fill((50, 50, 50)) + + # Draw the GUI window. + PyGUI.Begin("Ball Controls", 10, 10) + ball_speed = PyGUI.Slider("Speed", ball_speed, 1, 10) + ball_direction = PyGUI.Slider("Direction", ball_direction, 0, 360) + ball_radius = PyGUI.Slider("Radius", ball_radius, 10, 100) + ball_color_r = PyGUI.Slider("Red", ball_color_r, 0, 255) + ball_color_g = PyGUI.Slider("Green", ball_color_g, 0, 255) + ball_color_b = PyGUI.Slider("Blue", ball_color_b, 0, 255) + bounce_enabled = PyGUI.Checkbox("Bounce", bounce_enabled) + if PyGUI.Button("Reset Position"): + ball.pos = [WIDTH // 2, HEIGHT // 2] + PyGUI.End() + + # Update the ball parameters. + ball.speed = ball_speed + ball.direction = ball_direction + ball.radius = int(ball_radius) + ball.color = (int(ball_color_r), int(ball_color_g), int(ball_color_b)) + + ball.draw(screen) + + pygame.display.flip() + clock.tick(60) + +if __name__ == "__main__": + main() diff --git a/pygame-imgui-v2/imgui.py b/pygame-imgui-v2/imgui.py new file mode 100644 index 0000000..2e80d48 --- /dev/null +++ b/pygame-imgui-v2/imgui.py @@ -0,0 +1,256 @@ +import pygame + +class PyGUI: + _surface = None + _font = None + _events = [] + _window_stack = [] + + # UI layout constants + TITLE_BAR_HEIGHT = 30 + PADDING = 10 + SPACING = 5 + BOTTOM_PADDING = 10 + MIN_WINDOW_WIDTH = 220 + + # Colors and style + WINDOW_BG_COLOR = (60, 60, 60) + TITLE_BAR_COLOR = (30, 30, 30) + BORDER_COLOR = (20, 20, 20) + BUTTON_COLOR = (100, 100, 100) + BUTTON_HOVER_COLOR = (130, 130, 130) + SLIDER_TRACK_COLOR = (100, 100, 100) + SLIDER_KNOB_COLOR = (150, 150, 150) + WIDGET_TEXT_COLOR = (255, 255, 255) + CHECKBOX_BG_COLOR = (80, 80, 80) + CHECKBOX_CHECK_COLOR = (200, 200, 200) + + @staticmethod + def init(surface, font_name='Arial', font_size=18): + """Initialize PyGUI with the main Pygame surface and font.""" + PyGUI._surface = surface + PyGUI._font = pygame.font.SysFont(font_name, font_size) + + @staticmethod + def Begin(title, x, y): + """ + Begin a new draggable, auto–sizing window. + A new window context is created; widget calls will record + their draw commands and update the context layout. + """ + context = { + 'title': title, + 'x': x, + 'y': y, + 'width': PyGUI.MIN_WINDOW_WIDTH, + 'content_height': 0, # the vertical extent of all widgets + 'commands': [], # list of widget draw command lambdas + 'drag': False, + 'drag_offset': (0, 0), + 'local_y': 0 # current Y offset (relative to content area) + } + PyGUI._window_stack.append(context) + + @staticmethod + def End(): + """ + Ends the current window, calculates final dimensions, + draws the window background, title bar and border, then blits the window. + """ + if not PyGUI._window_stack: + return + context = PyGUI._window_stack.pop() + final_height = PyGUI.TITLE_BAR_HEIGHT + context['content_height'] + PyGUI.BOTTOM_PADDING + + # Create an offscreen surface for the entire window with alpha support. + window_surf = pygame.Surface((context['width'], final_height), pygame.SRCALPHA) + # Draw the window background with rounded corners. + pygame.draw.rect(window_surf, PyGUI.WINDOW_BG_COLOR, + (0, 0, context['width'], final_height), border_radius=8) + # Execute widget drawing commands onto window_surf. + for cmd in context['commands']: + cmd(window_surf) + # Draw the title bar on top. + title_bar_rect = pygame.Rect(0, 0, context['width'], PyGUI.TITLE_BAR_HEIGHT) + pygame.draw.rect(window_surf, PyGUI.TITLE_BAR_COLOR, title_bar_rect, border_radius=8) + # Render the window title. + title_surf = PyGUI._font.render(context['title'], True, PyGUI.WIDGET_TEXT_COLOR) + window_surf.blit(title_surf, (PyGUI.PADDING, (PyGUI.TITLE_BAR_HEIGHT - title_surf.get_height()) // 2)) + # Draw a border around the entire window. + pygame.draw.rect(window_surf, PyGUI.BORDER_COLOR, + (0, 0, context['width'], final_height), 2, border_radius=8) + # Blit the finished window onto the main surface. + PyGUI._surface.blit(window_surf, (context['x'], context['y'])) + + @staticmethod + def _current_window(): + if not PyGUI._window_stack: + return None + return PyGUI._window_stack[-1] + + @staticmethod + def Update(events): + """Update the internal event cache and process dragging for all windows.""" + PyGUI._events = events + for context in PyGUI._window_stack: + PyGUI._process_dragging(context) + + @staticmethod + def _process_dragging(context): + """If the mouse clicks the title bar, allow dragging to update window position.""" + mouse_pos = pygame.mouse.get_pos() + mouse_buttons = pygame.mouse.get_pressed() + title_rect = pygame.Rect(context['x'], context['y'], context['width'], PyGUI.TITLE_BAR_HEIGHT) + for event in PyGUI._events: + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + if title_rect.collidepoint(mouse_pos): + context['drag'] = True + context['drag_offset'] = (mouse_pos[0] - context['x'], + mouse_pos[1] - context['y']) + if event.type == pygame.MOUSEBUTTONUP and event.button == 1: + context['drag'] = False + if context['drag'] and mouse_buttons[0]: + context['x'] = mouse_pos[0] - context['drag_offset'][0] + context['y'] = mouse_pos[1] - context['drag_offset'][1] + + @staticmethod + def Button(label, width=100, height=30): + """ + Create a button widget; returns True if the button is clicked. + The interactive test is done immediately and the draw command is recorded. + """ + context = PyGUI._current_window() + if context is None: + return False + + # Compute local widget position inside the window's content area. + local_x = PyGUI.PADDING + local_y = context['local_y'] + PyGUI.PADDING + # Calculate absolute position for interaction. + abs_x = context['x'] + local_x + abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y + widget_rect = pygame.Rect(abs_x, abs_y, width, height) + + clicked = False + hovered = widget_rect.collidepoint(pygame.mouse.get_pos()) + for event in PyGUI._events: + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and hovered: + clicked = True + + color = PyGUI.BUTTON_HOVER_COLOR if hovered else PyGUI.BUTTON_COLOR + + # Record the draw command (drawing relative to the window surface). + def draw_command(surf): + btn_rect = pygame.Rect(local_x, local_y, width, height) + pygame.draw.rect(surf, color, btn_rect, border_radius=4) + text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR) + text_rect = text_surf.get_rect(center=btn_rect.center) + surf.blit(text_surf, text_rect) + + context['commands'].append(draw_command) + context['local_y'] += height + PyGUI.SPACING + context['content_height'] = max(context['content_height'], + context['local_y'] + PyGUI.PADDING) + needed_width = local_x + width + PyGUI.PADDING + if needed_width > context['width']: + context['width'] = needed_width + + return clicked + + @staticmethod + def Slider(label, value, min_val, max_val, width=150, height=20): + """ + Create a slider widget; returns the updated value. + A label is drawn above the slider track. + """ + context = PyGUI._current_window() + if context is None: + return value + + local_x = PyGUI.PADDING + local_y = context['local_y'] + PyGUI.PADDING + abs_x = context['x'] + local_x + abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y + + # Render label (with current value) and get its height. + label_surf = PyGUI._font.render(f"{label}: {value:.2f}", True, PyGUI.WIDGET_TEXT_COLOR) + label_height = label_surf.get_height() + + # Define slider track's absolute rectangle. + track_rect = pygame.Rect(abs_x, abs_y + label_height + 2, width, height) + new_value = value + if track_rect.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]: + mouse_x = pygame.mouse.get_pos()[0] + relative = (mouse_x - track_rect.x) / track_rect.width + relative = max(0.0, min(1.0, relative)) + new_value = min_val + relative * (max_val - min_val) + + relative = (new_value - min_val) / (max_val - min_val) + + def draw_command(surf): + # Draw the label. + surf.blit(label_surf, (local_x, local_y)) + # Draw the slider track. + local_track_rect = pygame.Rect(local_x, local_y + label_height + 2, width, height) + pygame.draw.rect(surf, PyGUI.SLIDER_TRACK_COLOR, local_track_rect, border_radius=4) + # Draw the knob. + knob_rect = pygame.Rect(local_track_rect.x + int(relative * local_track_rect.width) - 5, + local_track_rect.y - 2, 10, local_track_rect.height + 4) + pygame.draw.rect(surf, PyGUI.SLIDER_KNOB_COLOR, knob_rect, border_radius=4) + + context['commands'].append(draw_command) + total_height = label_height + 2 + height + PyGUI.SPACING + context['local_y'] += total_height + context['content_height'] = max(context['content_height'], + context['local_y'] + PyGUI.PADDING) + if local_x + width + PyGUI.PADDING > context['width']: + context['width'] = local_x + width + PyGUI.PADDING + + return new_value + + @staticmethod + def Checkbox(label, value): + """ + Create a checkbox widget; returns the toggled boolean value. + """ + context = PyGUI._current_window() + if context is None: + return value + + local_x = PyGUI.PADDING + local_y = context['local_y'] + PyGUI.PADDING + abs_x = context['x'] + local_x + abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y + + box_size = 20 + box_rect = pygame.Rect(abs_x, abs_y, box_size, box_size) + new_value = value + hovered = box_rect.collidepoint(pygame.mouse.get_pos()) + for event in PyGUI._events: + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and hovered: + new_value = not value + + def draw_command(surf): + local_box_rect = pygame.Rect(local_x, local_y, box_size, box_size) + pygame.draw.rect(surf, PyGUI.CHECKBOX_BG_COLOR, local_box_rect, border_radius=3) + if new_value: + pygame.draw.line(surf, PyGUI.CHECKBOX_CHECK_COLOR, + (local_box_rect.left, local_box_rect.top), + (local_box_rect.right, local_box_rect.bottom), 2) + pygame.draw.line(surf, PyGUI.CHECKBOX_CHECK_COLOR, + (local_box_rect.left, local_box_rect.bottom), + (local_box_rect.right, local_box_rect.top), 2) + text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR) + surf.blit(text_surf, (local_box_rect.right + 5, local_box_rect.top)) + + context['commands'].append(draw_command) + text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR) + line_height = max(box_size, text_surf.get_height()) + context['local_y'] += line_height + PyGUI.SPACING + context['content_height'] = max(context['content_height'], + context['local_y'] + PyGUI.PADDING) + needed_width = local_x + box_size + 5 + text_surf.get_width() + PyGUI.PADDING + if needed_width > context['width']: + context['width'] = needed_width + + return new_value diff --git a/pygame-imgui/example.py b/pygame-imgui/example.py index ef3e189..21844c8 100644 --- a/pygame-imgui/example.py +++ b/pygame-imgui/example.py @@ -1,7 +1,7 @@ import pygame import sys import math -import pygui +from pygui import PyGUI from pygui_pygame_backend import Render, Rect, get_mouse_pos, get_mouse_pressed, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION import pygui_pygame_backend as backend @@ -13,7 +13,7 @@ clock = pygame.time.Clock() # Create the backend renderer and assign the backend to PyGUI. renderer = Render(screen) -pygui.PyGUI.set_backend(backend, renderer) +PyGUI.set_backend(backend, renderer) # --------------------- # Ball Simulation @@ -51,22 +51,22 @@ while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False - pygui.PyGUI.handle_event(event) + PyGUI.handle_event(event) screen.fill((30, 30, 30)) # Draw a draggable, auto–sizing GUI window. - pygui.PyGUI.Begin("Ball Controls", 10, 10) - ball_speed = pygui.PyGUI.Slider("Speed", ball_speed, 1, 10) - ball_direction = pygui.PyGUI.Slider("Direction", ball_direction, 0, 360) - ball_radius = pygui.PyGUI.Slider("Radius", ball_radius, 10, 100) - ball_color_r = pygui.PyGUI.Slider("Red", ball_color_r, 0, 255) - ball_color_g = pygui.PyGUI.Slider("Green", ball_color_g, 0, 255) - ball_color_b = pygui.PyGUI.Slider("Blue", ball_color_b, 0, 255) - bounce_enabled = pygui.PyGUI.Checkbox("Bounce", bounce_enabled) - if pygui.PyGUI.Button("Reset Position"): + PyGUI.Begin("Ball Controls", 10, 10) + ball_speed = PyGUI.Slider("Speed", ball_speed, 1, 10) + ball_direction = PyGUI.Slider("Direction", ball_direction, 0, 360) + ball_radius = PyGUI.Slider("Radius", ball_radius, 10, 100) + ball_color_r = PyGUI.Slider("Red", ball_color_r, 0, 255) + ball_color_g = PyGUI.Slider("Green", ball_color_g, 0, 255) + ball_color_b = PyGUI.Slider("Blue", ball_color_b, 0, 255) + bounce_enabled = PyGUI.Checkbox("Bounce", bounce_enabled) + if PyGUI.Button("Reset Position"): ball.pos = [WIDTH // 2, HEIGHT // 2] - pygui.PyGUI.End() + PyGUI.End() rad = math.radians(ball_direction) ball.velocity[0] = ball_speed * math.cos(rad) @@ -81,7 +81,7 @@ while running: ball.draw(screen) pygame.display.flip() - pygui.PyGUI.update() + PyGUI.update() pygame.quit() sys.exit()