diff --git a/CytoSim/README.md b/CytoSim/README.md new file mode 100644 index 0000000..e141f72 --- /dev/null +++ b/CytoSim/README.md @@ -0,0 +1,2 @@ +# CytoSim + diff --git a/CytoSim/__pycache__/particle.cpython-312.pyc b/CytoSim/__pycache__/particle.cpython-312.pyc new file mode 100644 index 0000000..fec9411 Binary files /dev/null and b/CytoSim/__pycache__/particle.cpython-312.pyc differ diff --git a/CytoSim/__pycache__/sensor.cpython-312.pyc b/CytoSim/__pycache__/sensor.cpython-312.pyc new file mode 100644 index 0000000..b98724e Binary files /dev/null and b/CytoSim/__pycache__/sensor.cpython-312.pyc differ diff --git a/CytoSim/__pycache__/slider.cpython-312.pyc b/CytoSim/__pycache__/slider.cpython-312.pyc new file mode 100644 index 0000000..ca00cce Binary files /dev/null and b/CytoSim/__pycache__/slider.cpython-312.pyc differ diff --git a/CytoSim/main.py b/CytoSim/main.py new file mode 100644 index 0000000..cb37ebf --- /dev/null +++ b/CytoSim/main.py @@ -0,0 +1,168 @@ +import pygame +import numpy as np +import matplotlib.pyplot as plt +import math +from particle import Particle +from sensor import Sensor +from slider import Slider + +pygame.init() + +SCREEN_WIDTH = 800 +SCREEN_HEIGHT = 600 + +SENSOR_DISTANCE = 200 +REST_MEDIUM = 180000 + +y_lim = 40000 +y_lim2 = 0.000000000005 + + +screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) + +sensor = Sensor(width = 50, distance = SENSOR_DISTANCE, space = 300) +sensor.inputVoltage(5, -5) + + + +silica = Particle(speed = 1, size = 60, perm = 4, rest = pow(10, 12)) + +time = .1 +time_data = [] +volume_data = [] +sensor_data = [] +rest_data = [] +current1_data = [] +current2_data = [] + + +plt.ion() +fig, (ax, ax2) = plt.subplots(2, 1, figsize=(10, 10)) +line, = ax.plot([], [], 'r-') +line2, = ax.plot([], [], 'g-') +line3, = ax2.plot([], [], 'b-') +line4, = ax2.plot([], [], 'g-') +ax.set_xlim(0, 900) +ax.set_ylim(-0.01, y_lim) +ax.set_xlabel('Time (s)') +ax.set_ylabel('Volume') +ax.set_title('Volume/time') + +ax2.set_xlim(0, 900) +ax2.set_ylim(-1 * y_lim2, y_lim2) +ax2.set_xlabel('Time (s)') +ax2.set_ylabel('Current') +ax2.set_title('Current/time') + +slider1 = Slider(20, 20, 100, 20, 20, SENSOR_DISTANCE / 2, 80) +slider2 = Slider(20, 50, 100, 20, .1, 10, 1) +slider3 = Slider(20, 80, 100, 20, 1, 100, 10) + +run = True +while run: + + timeScale = slider2.value + sensor.inputVoltage(slider3.value, -1 * slider3.value) + + distance = silica.move(time) + if distance > SCREEN_WIDTH + (silica.size * 2): + time =.1 + time_data = [] + volume_data = [] + sensor_data = [] + rest_data = [] + current1_data = [] + current2_data = [] + + screen.fill((0,0,0)) + + sensor.generate(SCREEN_WIDTH, SCREEN_HEIGHT, screen) + + pygame.draw.circle(screen, (255, 255, 255), (distance - silica.size, 300), silica.size) + pygame.draw.circle(screen, (0,255,0), (distance - silica.size, 300), 10) + + slider1.draw(screen) + slider2.draw(screen) + slider3.draw(screen) + + silica.updateSize(slider1.value) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + slider1.handle_event(event) + slider2.handle_event(event) + slider3.handle_event(event) + + volume = sensor.getParticleVolume(distance, silica) + + sensor_data_volume = sensor.volume - volume + sensor_data.append(sensor_data_volume) + + sensor_resistance = REST_MEDIUM * ((pow(sensor.distance, 2) * pow(10, -18)) / (sensor_data_volume * pow(10, -27))) + nom_sens_res = REST_MEDIUM * ((sensor.distance * pow(10, -9)) / (sensor.width * sensor.distance * pow(10, -18))) + + if volume: + particle_resistance = silica.rest * pow((3/(16 * pow(math.pi, 2) * volume * pow(10, -9))), 1/3) + total_resistance_inv = (1 / particle_resistance) + (1 / sensor_resistance) + else: + particle_resistance = 0 + total_resistance_inv = 1 / sensor_resistance + + total_resistance = 1 / total_resistance_inv + + current1 = 0 + current2 = 0 + + + which_sensor = sensor.whichSensor(distance, silica) + if which_sensor == 1: + current1 = sensor.voltage1 / total_resistance + current2 = sensor.voltage2 / nom_sens_res + elif which_sensor == 2: + current2 = sensor.voltage2 / total_resistance + current1 = sensor.voltage1 / nom_sens_res + else: + current1 = sensor.voltage1 / nom_sens_res + current2 = sensor.voltage2 / nom_sens_res + + current1_data.append(current1) + current2_data.append(current2) + print(f"{current1} = {sensor.voltage1} / {total_resistance}") + rest_data.append(total_resistance) + + if (volume > y_lim): + y_lim = volume + (volume * 1.2) + ax.set_ylim(-1000, y_lim) + + if (current1 > y_lim2): + y_lim2 = current1 + (current1 * 1.2) + ax2.set_ylim(-1 * y_lim2, y_lim2) + + + + time_data.append(time) + volume_data.append(volume) + + line.set_xdata(time_data) + line.set_ydata(volume_data) + line2.set_xdata(time_data) + line2.set_ydata(sensor_data) + line3.set_xdata(time_data) + line3.set_ydata(current1_data) + line4.set_xdata(time_data) + line4.set_ydata(current2_data) + ax.relim() + ax.autoscale_view() + ax2.relim() + ax2.autoscale_view() + plt.draw() + plt.pause(0.01) + + pygame.display.update() + + time = timeScale + time + +pygame.quit() + + diff --git a/CytoSim/particle.py b/CytoSim/particle.py new file mode 100644 index 0000000..338b020 --- /dev/null +++ b/CytoSim/particle.py @@ -0,0 +1,23 @@ +import math + +class Particle: + def __init__(self, speed, size, perm, rest): + self.speed = speed + self.size = size + self.perm = perm + self.rest = rest + self.volume = (4/3.0) * math.pi * size * size * size + + def move(self, time): + distance = self.speed * time + return distance + + def partialVol(self, height): + partialVol = (1/3) * math.pi * height * height * ((3 * self.size) - height) + return partialVol + + def updateSize(self, size): + self.size = size + self.volume = (4/3) * math.pi * size * size * size + + diff --git a/CytoSim/sensor.py b/CytoSim/sensor.py new file mode 100644 index 0000000..6e53002 --- /dev/null +++ b/CytoSim/sensor.py @@ -0,0 +1,95 @@ +import pygame + +class Sensor: + def __init__(self, width, distance, space): + self.width = width + self.distance = distance + self.space = space + self.volume = width * pow(distance, 2) + + def generate(self, screenWidth, screenHeight, screen): + self.sensor1_x = (screenWidth / 2) - (self.space / 2) - self.width + self.sensor1_y = 0 + self.sensor1_x_size = self.width + self.sensor1_y_size = (screenHeight / 2) - (self.distance / 2) + + self.inner1 = self.sensor1_x + self.outer1 = self.inner1 + self.width + + sensor1a = pygame.Rect(self.sensor1_x, self.sensor1_y, self.sensor1_x_size, self.sensor1_y_size) + sensor1b = pygame.Rect(self.sensor1_x, self.sensor1_y + self.sensor1_y_size + self.distance, self.sensor1_x_size, self.sensor1_y_size) + pygame.draw.rect(screen, (0, 0, 255), sensor1a) + pygame.draw.rect(screen, (0, 0, 255), sensor1b) + + self.sensor2_x = (screenWidth / 2) + (self.space / 2) + self.sensor2_y = 0 + self.sensor2_x_size = self.width + self.sensor2_y_size = (screenHeight / 2) - (self.distance / 2) + + self.inner2 = self.sensor2_x + self.outer2 = self.inner2 + self.width + + sensor2a = pygame.Rect(self.sensor2_x, self.sensor2_y, self.sensor2_x_size, self.sensor2_y_size) + sensor2b = pygame.Rect(self.sensor2_x, self.sensor2_y + self.sensor2_y_size + self.distance, self.sensor2_x_size, self.sensor2_y_size) + pygame.draw.rect(screen, (0, 0, 255), sensor2a) + pygame.draw.rect(screen, (0, 0, 255), sensor2b) + + def testSensor1(self, partCenter, particle): + if (particle.size >= abs(self.inner1 - (partCenter - particle.size))) and (particle.size >= abs(self.outer1 - (partCenter - particle.size))): + volume = ((particle.volume / 2) - (particle.partialVol(particle.size - ((partCenter - particle.size) - self.inner1)))) + ((particle.volume / 2) - particle.partialVol(particle.size - (self.outer1 - (partCenter - particle.size)))) + return volume + elif particle.size >= abs(self.inner1 - (partCenter - particle.size)): + volume = particle.partialVol(particle.size - (self.inner1 - (partCenter - particle.size))) + return volume + elif particle.size >= abs(self.outer1 - (partCenter - particle.size)): + volume = particle.volume - particle.partialVol(particle.size - (self.outer1 - (partCenter - particle.size))) + return volume + elif ((partCenter - particle.size) >= self.inner1 and (partCenter - particle.size) <= self.outer1): + volume = particle.volume + return volume + else: + return 0 + + def testSensor2(self, partCenter, particle): + if (particle.size >= abs(self.inner2 - (partCenter - particle.size))) and (particle.size >= abs(self.outer2 - (partCenter - particle.size))): + volume = ((particle.volume / 2) - (particle.partialVol(particle.size - ((partCenter - particle.size) - self.inner2)))) + ((particle.volume / 2) - particle.partialVol(particle.size - (self.outer2 - (partCenter - particle.size)))) + return volume + elif particle.size >= abs(self.inner2 - (partCenter - particle.size)): + volume = particle.partialVol(particle.size - (self.inner2 - (partCenter - particle.size))) + return volume + elif particle.size >= abs(self.outer2 - (partCenter - particle.size)): + volume = particle.volume - particle.partialVol(particle.size - (self.outer2 - (partCenter - particle.size))) + return volume + elif ((partCenter - particle.size) >= self.inner2 and (partCenter - particle.size) <= self.outer2): + volume = particle.volume + return volume + else: + return 0 + + def getParticleVolume(self, partCenter, particle): + volume1 = self.testSensor1(partCenter, particle) + #volume1 = 0 + volume2 = self.testSensor2(partCenter, particle) + + if volume1: + return volume1 + elif volume2: + return volume2 + else: + return 0 + + def whichSensor(self, partCenter, particle): + volume1 = self.testSensor1(partCenter, particle) + #volume1 = 0 + volume2 = self.testSensor2(partCenter, particle) + + if volume1: + return 1 + elif volume2: + return 2 + else: + return 0 + + def inputVoltage(self, voltage1, voltage2): + self.voltage1 = voltage1 + self.voltage2 = voltage2 diff --git a/CytoSim/slider.py b/CytoSim/slider.py new file mode 100644 index 0000000..304e27d --- /dev/null +++ b/CytoSim/slider.py @@ -0,0 +1,35 @@ +import pygame + +WHITE = (255, 255, 255) +GRAY = (200, 200, 200) +BLACK = (0, 0, 0) +RED = (255, 0, 0) + +class Slider: + + def __init__(self, x, y, w, h, min_val, max_val, initial_val): + self.rect = pygame.Rect(x, y, w, h) + self.min_val = min_val + self.max_val = max_val + self.value = initial_val + self.grabbed = False + + def draw(self, screen): + # Draw the background + pygame.draw.rect(screen, GRAY, self.rect) + # Draw the handle (circle) + handle_x = self.rect.x + (self.value - self.min_val) / (self.max_val - self.min_val) * self.rect.width + pygame.draw.circle(screen, RED, (int(handle_x), self.rect.centery), self.rect.height // 2) + + def handle_event(self, event): + if event.type == pygame.MOUSEBUTTONDOWN: + if self.rect.collidepoint(event.pos): + self.grabbed = True + elif event.type == pygame.MOUSEBUTTONUP: + self.grabbed = False + elif event.type == pygame.MOUSEMOTION: + if self.grabbed: + mouse_x = event.pos[0] + # Constrain the handle within the slider + new_value = (mouse_x - self.rect.x) / self.rect.width * (self.max_val - self.min_val) + self.min_val + self.value = max(self.min_val, min(self.max_val, new_value)) diff --git a/__pycache__/particle.cpython-312.pyc b/__pycache__/particle.cpython-312.pyc new file mode 100644 index 0000000..fec9411 Binary files /dev/null and b/__pycache__/particle.cpython-312.pyc differ diff --git a/__pycache__/sensor.cpython-312.pyc b/__pycache__/sensor.cpython-312.pyc new file mode 100644 index 0000000..b98724e Binary files /dev/null and b/__pycache__/sensor.cpython-312.pyc differ diff --git a/__pycache__/slider.cpython-312.pyc b/__pycache__/slider.cpython-312.pyc new file mode 100644 index 0000000..ca00cce Binary files /dev/null and b/__pycache__/slider.cpython-312.pyc differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..cb37ebf --- /dev/null +++ b/main.py @@ -0,0 +1,168 @@ +import pygame +import numpy as np +import matplotlib.pyplot as plt +import math +from particle import Particle +from sensor import Sensor +from slider import Slider + +pygame.init() + +SCREEN_WIDTH = 800 +SCREEN_HEIGHT = 600 + +SENSOR_DISTANCE = 200 +REST_MEDIUM = 180000 + +y_lim = 40000 +y_lim2 = 0.000000000005 + + +screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) + +sensor = Sensor(width = 50, distance = SENSOR_DISTANCE, space = 300) +sensor.inputVoltage(5, -5) + + + +silica = Particle(speed = 1, size = 60, perm = 4, rest = pow(10, 12)) + +time = .1 +time_data = [] +volume_data = [] +sensor_data = [] +rest_data = [] +current1_data = [] +current2_data = [] + + +plt.ion() +fig, (ax, ax2) = plt.subplots(2, 1, figsize=(10, 10)) +line, = ax.plot([], [], 'r-') +line2, = ax.plot([], [], 'g-') +line3, = ax2.plot([], [], 'b-') +line4, = ax2.plot([], [], 'g-') +ax.set_xlim(0, 900) +ax.set_ylim(-0.01, y_lim) +ax.set_xlabel('Time (s)') +ax.set_ylabel('Volume') +ax.set_title('Volume/time') + +ax2.set_xlim(0, 900) +ax2.set_ylim(-1 * y_lim2, y_lim2) +ax2.set_xlabel('Time (s)') +ax2.set_ylabel('Current') +ax2.set_title('Current/time') + +slider1 = Slider(20, 20, 100, 20, 20, SENSOR_DISTANCE / 2, 80) +slider2 = Slider(20, 50, 100, 20, .1, 10, 1) +slider3 = Slider(20, 80, 100, 20, 1, 100, 10) + +run = True +while run: + + timeScale = slider2.value + sensor.inputVoltage(slider3.value, -1 * slider3.value) + + distance = silica.move(time) + if distance > SCREEN_WIDTH + (silica.size * 2): + time =.1 + time_data = [] + volume_data = [] + sensor_data = [] + rest_data = [] + current1_data = [] + current2_data = [] + + screen.fill((0,0,0)) + + sensor.generate(SCREEN_WIDTH, SCREEN_HEIGHT, screen) + + pygame.draw.circle(screen, (255, 255, 255), (distance - silica.size, 300), silica.size) + pygame.draw.circle(screen, (0,255,0), (distance - silica.size, 300), 10) + + slider1.draw(screen) + slider2.draw(screen) + slider3.draw(screen) + + silica.updateSize(slider1.value) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + slider1.handle_event(event) + slider2.handle_event(event) + slider3.handle_event(event) + + volume = sensor.getParticleVolume(distance, silica) + + sensor_data_volume = sensor.volume - volume + sensor_data.append(sensor_data_volume) + + sensor_resistance = REST_MEDIUM * ((pow(sensor.distance, 2) * pow(10, -18)) / (sensor_data_volume * pow(10, -27))) + nom_sens_res = REST_MEDIUM * ((sensor.distance * pow(10, -9)) / (sensor.width * sensor.distance * pow(10, -18))) + + if volume: + particle_resistance = silica.rest * pow((3/(16 * pow(math.pi, 2) * volume * pow(10, -9))), 1/3) + total_resistance_inv = (1 / particle_resistance) + (1 / sensor_resistance) + else: + particle_resistance = 0 + total_resistance_inv = 1 / sensor_resistance + + total_resistance = 1 / total_resistance_inv + + current1 = 0 + current2 = 0 + + + which_sensor = sensor.whichSensor(distance, silica) + if which_sensor == 1: + current1 = sensor.voltage1 / total_resistance + current2 = sensor.voltage2 / nom_sens_res + elif which_sensor == 2: + current2 = sensor.voltage2 / total_resistance + current1 = sensor.voltage1 / nom_sens_res + else: + current1 = sensor.voltage1 / nom_sens_res + current2 = sensor.voltage2 / nom_sens_res + + current1_data.append(current1) + current2_data.append(current2) + print(f"{current1} = {sensor.voltage1} / {total_resistance}") + rest_data.append(total_resistance) + + if (volume > y_lim): + y_lim = volume + (volume * 1.2) + ax.set_ylim(-1000, y_lim) + + if (current1 > y_lim2): + y_lim2 = current1 + (current1 * 1.2) + ax2.set_ylim(-1 * y_lim2, y_lim2) + + + + time_data.append(time) + volume_data.append(volume) + + line.set_xdata(time_data) + line.set_ydata(volume_data) + line2.set_xdata(time_data) + line2.set_ydata(sensor_data) + line3.set_xdata(time_data) + line3.set_ydata(current1_data) + line4.set_xdata(time_data) + line4.set_ydata(current2_data) + ax.relim() + ax.autoscale_view() + ax2.relim() + ax2.autoscale_view() + plt.draw() + plt.pause(0.01) + + pygame.display.update() + + time = timeScale + time + +pygame.quit() + + diff --git a/particle.py b/particle.py new file mode 100644 index 0000000..338b020 --- /dev/null +++ b/particle.py @@ -0,0 +1,23 @@ +import math + +class Particle: + def __init__(self, speed, size, perm, rest): + self.speed = speed + self.size = size + self.perm = perm + self.rest = rest + self.volume = (4/3.0) * math.pi * size * size * size + + def move(self, time): + distance = self.speed * time + return distance + + def partialVol(self, height): + partialVol = (1/3) * math.pi * height * height * ((3 * self.size) - height) + return partialVol + + def updateSize(self, size): + self.size = size + self.volume = (4/3) * math.pi * size * size * size + + diff --git a/pics/Figure_1.png b/pics/Figure_1.png new file mode 100644 index 0000000..c6aa4f5 Binary files /dev/null and b/pics/Figure_1.png differ diff --git a/sensor.py b/sensor.py new file mode 100644 index 0000000..6e53002 --- /dev/null +++ b/sensor.py @@ -0,0 +1,95 @@ +import pygame + +class Sensor: + def __init__(self, width, distance, space): + self.width = width + self.distance = distance + self.space = space + self.volume = width * pow(distance, 2) + + def generate(self, screenWidth, screenHeight, screen): + self.sensor1_x = (screenWidth / 2) - (self.space / 2) - self.width + self.sensor1_y = 0 + self.sensor1_x_size = self.width + self.sensor1_y_size = (screenHeight / 2) - (self.distance / 2) + + self.inner1 = self.sensor1_x + self.outer1 = self.inner1 + self.width + + sensor1a = pygame.Rect(self.sensor1_x, self.sensor1_y, self.sensor1_x_size, self.sensor1_y_size) + sensor1b = pygame.Rect(self.sensor1_x, self.sensor1_y + self.sensor1_y_size + self.distance, self.sensor1_x_size, self.sensor1_y_size) + pygame.draw.rect(screen, (0, 0, 255), sensor1a) + pygame.draw.rect(screen, (0, 0, 255), sensor1b) + + self.sensor2_x = (screenWidth / 2) + (self.space / 2) + self.sensor2_y = 0 + self.sensor2_x_size = self.width + self.sensor2_y_size = (screenHeight / 2) - (self.distance / 2) + + self.inner2 = self.sensor2_x + self.outer2 = self.inner2 + self.width + + sensor2a = pygame.Rect(self.sensor2_x, self.sensor2_y, self.sensor2_x_size, self.sensor2_y_size) + sensor2b = pygame.Rect(self.sensor2_x, self.sensor2_y + self.sensor2_y_size + self.distance, self.sensor2_x_size, self.sensor2_y_size) + pygame.draw.rect(screen, (0, 0, 255), sensor2a) + pygame.draw.rect(screen, (0, 0, 255), sensor2b) + + def testSensor1(self, partCenter, particle): + if (particle.size >= abs(self.inner1 - (partCenter - particle.size))) and (particle.size >= abs(self.outer1 - (partCenter - particle.size))): + volume = ((particle.volume / 2) - (particle.partialVol(particle.size - ((partCenter - particle.size) - self.inner1)))) + ((particle.volume / 2) - particle.partialVol(particle.size - (self.outer1 - (partCenter - particle.size)))) + return volume + elif particle.size >= abs(self.inner1 - (partCenter - particle.size)): + volume = particle.partialVol(particle.size - (self.inner1 - (partCenter - particle.size))) + return volume + elif particle.size >= abs(self.outer1 - (partCenter - particle.size)): + volume = particle.volume - particle.partialVol(particle.size - (self.outer1 - (partCenter - particle.size))) + return volume + elif ((partCenter - particle.size) >= self.inner1 and (partCenter - particle.size) <= self.outer1): + volume = particle.volume + return volume + else: + return 0 + + def testSensor2(self, partCenter, particle): + if (particle.size >= abs(self.inner2 - (partCenter - particle.size))) and (particle.size >= abs(self.outer2 - (partCenter - particle.size))): + volume = ((particle.volume / 2) - (particle.partialVol(particle.size - ((partCenter - particle.size) - self.inner2)))) + ((particle.volume / 2) - particle.partialVol(particle.size - (self.outer2 - (partCenter - particle.size)))) + return volume + elif particle.size >= abs(self.inner2 - (partCenter - particle.size)): + volume = particle.partialVol(particle.size - (self.inner2 - (partCenter - particle.size))) + return volume + elif particle.size >= abs(self.outer2 - (partCenter - particle.size)): + volume = particle.volume - particle.partialVol(particle.size - (self.outer2 - (partCenter - particle.size))) + return volume + elif ((partCenter - particle.size) >= self.inner2 and (partCenter - particle.size) <= self.outer2): + volume = particle.volume + return volume + else: + return 0 + + def getParticleVolume(self, partCenter, particle): + volume1 = self.testSensor1(partCenter, particle) + #volume1 = 0 + volume2 = self.testSensor2(partCenter, particle) + + if volume1: + return volume1 + elif volume2: + return volume2 + else: + return 0 + + def whichSensor(self, partCenter, particle): + volume1 = self.testSensor1(partCenter, particle) + #volume1 = 0 + volume2 = self.testSensor2(partCenter, particle) + + if volume1: + return 1 + elif volume2: + return 2 + else: + return 0 + + def inputVoltage(self, voltage1, voltage2): + self.voltage1 = voltage1 + self.voltage2 = voltage2 diff --git a/slider.py b/slider.py new file mode 100644 index 0000000..304e27d --- /dev/null +++ b/slider.py @@ -0,0 +1,35 @@ +import pygame + +WHITE = (255, 255, 255) +GRAY = (200, 200, 200) +BLACK = (0, 0, 0) +RED = (255, 0, 0) + +class Slider: + + def __init__(self, x, y, w, h, min_val, max_val, initial_val): + self.rect = pygame.Rect(x, y, w, h) + self.min_val = min_val + self.max_val = max_val + self.value = initial_val + self.grabbed = False + + def draw(self, screen): + # Draw the background + pygame.draw.rect(screen, GRAY, self.rect) + # Draw the handle (circle) + handle_x = self.rect.x + (self.value - self.min_val) / (self.max_val - self.min_val) * self.rect.width + pygame.draw.circle(screen, RED, (int(handle_x), self.rect.centery), self.rect.height // 2) + + def handle_event(self, event): + if event.type == pygame.MOUSEBUTTONDOWN: + if self.rect.collidepoint(event.pos): + self.grabbed = True + elif event.type == pygame.MOUSEBUTTONUP: + self.grabbed = False + elif event.type == pygame.MOUSEMOTION: + if self.grabbed: + mouse_x = event.pos[0] + # Constrain the handle within the slider + new_value = (mouse_x - self.rect.x) / self.rect.width * (self.max_val - self.min_val) + self.min_val + self.value = max(self.min_val, min(self.max_val, new_value))