imgui stuffz
This commit is contained in:
parent
9bab6a0265
commit
d54ecc33da
@ -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()
|
BIN
pygame-imgui-v2/__pycache__/imgui.cpython-311.pyc
Normal file
BIN
pygame-imgui-v2/__pycache__/imgui.cpython-311.pyc
Normal file
Binary file not shown.
72
pygame-imgui-v2/example.py
Normal file
72
pygame-imgui-v2/example.py
Normal file
@ -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()
|
256
pygame-imgui-v2/imgui.py
Normal file
256
pygame-imgui-v2/imgui.py
Normal file
@ -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
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user