diff --git a/typing_speed_test.py b/typing_speed_test.py index 9283494..298e6e4 100644 --- a/typing_speed_test.py +++ b/typing_speed_test.py @@ -1,401 +1,223 @@ import customtkinter as ctk import random import time -import threading -import urllib.request -import json -# --- Theme Configuration --- ctk.set_appearance_mode("light") ctk.set_default_color_theme("blue") -FALLBACK_SENTENCES = [ - "Technology is the most effective way to change the world.", - "Innovation is the ability to see change as an opportunity, not a threat.", - "Artificial Intelligence is not a threat to creativity; it is a catalyst for innovation.", - "Data is the canvas, and AI is the brush that paints the picture of insights.", - "Artificial Intelligence: where innovation meets computation in the pursuit of a smarter tomorrow." -] - - feature TEST_DURATION = 60 -class TypingSpeedTest: - def __init__(self, root): - self.root = root - self.root.title("Typing Speed Test") - self.root.geometry("600x560") - self.root.resizable(False, False) - - -QUOTE_API_URL = "http://api.quotable.io/random" +SENTENCES = [ + "Technology is the most effective way to change the world.", + "Innovation is the ability to see change as an opportunity.", + "Artificial Intelligence helps humans solve complex problems.", + "Data science is transforming industries across the globe.", + "Programming improves logical thinking and creativity." +] -THEMES = { - "Ocean": {"accent": "#3B8ED0", "bg": "#1a1a2e", "accent_hover": "#2775b3"}, - "Sunset": {"accent": "#E05C5C", "bg": "#2b1a1a", "accent_hover": "#b84444"}, - "Forest": {"accent": "#4CAF50", "bg": "#1a2b1a", "accent_hover": "#388E3C"}, - "Candy": {"accent": "#D45FBF", "bg": "#2b1a2b", "accent_hover": "#a8429b"}, - "Midnight": {"accent": "#A78BFA", "bg": "#0f0f1a", "accent_hover": "#7c5cbf"}, -} class TypingSpeedTest(ctk.CTk): + def __init__(self): super().__init__() - self.title("Typing Speed Test Pro") - self.geometry("700x550") - self.resizable(False, False) + self.title("Typing Speed Test") + self.geometry("700x500") - main - self.start_time = None self.current_sentence = "" + self.start_time = None self.time_left = TEST_DURATION - self.timer_id = None - - self.title_label = tk.Label(root, text="Typing Speed Test", font=("Helvetica", 18, "bold")) - self.title_label.pack(pady=10) - - feature - self.timer_label = tk.Label(root, text=f"Time Left: {self.time_left}s", font=("Helvetica", 14, "bold"), fg="blue") - - self.timer_label = tk.Label(root, text="⏱ Time: 00:00", font=("Helvetica", 14, "bold"), fg="blue") - main - self.timer_label.pack(pady=5) - - self.instruction_label = tk.Label(root, text="Type the exact sentence below as fast as you can:", font=("Helvetica", 14)) - self.instruction_label.pack(pady=10) - - self.sentence_label = tk.Label(root, text="", font=("Helvetica", 16), wraplength=550, justify="center") - self.sentence_label.pack(pady=10) - - self.input_textbox = tk.Text(root, font=("Helvetica", 14), width=50, height=2) - self.input_textbox.pack(pady=5) - - self.error_feedback_label = tk.Label(root, text="", font=("Helvetica", 10, "bold"), fg="orange") - self.error_feedback_label.pack() - - self.input_textbox.bind("", self.check_result) - self.input_textbox.bind("", self.validate_typing) - self.input_textbox.bind("", self.enable_button_after_typing, add="+") - self.input_textbox.tag_configure("correct", foreground="green") - self.input_textbox.tag_configure("incorrect", foreground="red") - self.timer_running = False - self.elapsed_seconds = 0 - # --- BUTTON SECTION --- - self.start_button = tk.Button(root, text="Start Test", font=("Helvetica", 14), command=self.start_test) - self.start_button.pack(pady=10) - - self.result_button = tk.Button(root, text="Display Results", font=("Helvetica", 14), command=self.check_result, state=tk.DISABLED) - self.result_button.pack(pady=10) - - self.result_label = tk.Label(root, text="", font=("Helvetica", 14, "italic"), fg="green") - self.time_left = 60 self.timer_running = False self.paused = False - self.pause_start = None - self.total_paused_time = 0 - self.scores = [] - self.mode = "60s" - self.current_theme = "Ocean" - - # --- UI --- - self.title_label = ctk.CTkLabel(self, text="Typing Speed Test", + self.countdown = 3 + + # TITLE + self.title_label = ctk.CTkLabel(self, + text="Typing Speed Test", font=("Helvetica", 28, "bold")) self.title_label.pack(pady=20) + # TIMER self.timer_label = ctk.CTkLabel(self, text="Time Remaining: 60s", - font=("Helvetica", 16, "bold"), - text_color="#3B8ED0") - self.timer_label.pack(pady=5) - - self.sentence_frame = ctk.CTkFrame(self, fg_color="transparent") - self.sentence_frame.pack(pady=20, padx=40) - - self.sentence_label = ctk.CTkLabel( - self.sentence_frame, - text="Press 'Start Test' to begin", - font=("Helvetica", 18), - wraplength=600, - justify="center", - text_color="gray" - ) - self.sentence_label.pack() + font=("Helvetica", 16, "bold")) + self.timer_label.pack() + + # SENTENCE + self.sentence_label = ctk.CTkLabel(self, + text="Press Start to begin", + wraplength=600, + font=("Helvetica", 18)) + self.sentence_label.pack(pady=20) + # INPUT BOX self.input_textbox = ctk.CTkTextbox(self, - font=("Helvetica", 16), - width=550, - height=100, - border_width=2) + width=600, + height=120, + font=("Helvetica", 16)) self.input_textbox.pack(pady=10) - self.input_textbox.bind("", self.handle_input) + self.input_textbox.configure(state="disabled") - self.feedback_label = ctk.CTkLabel(self, - text="", - font=("Helvetica", 12, "bold")) - self.feedback_label.pack() + # RESULT + self.result_label = ctk.CTkLabel(self, + text="", + font=("Helvetica", 18)) + self.result_label.pack(pady=10) - # Buttons - self.button_frame = ctk.CTkFrame(self, fg_color="transparent") + # BUTTON FRAME + self.button_frame = ctk.CTkFrame(self) self.button_frame.pack(pady=10) - self.start_button = ctk.CTkButton( - self.button_frame, - text="Start Test", - font=("Helvetica", 14, "bold"), - command=self.start_test, - height=40 - ) + # START BUTTON + self.start_button = ctk.CTkButton(self.button_frame, + text="Start Test", + command=self.start_test) self.start_button.grid(row=0, column=0, padx=10) - self.result_button = ctk.CTkButton( - self.button_frame, - text="Display Results", - font=("Helvetica", 14, "bold"), - command=self.check_result, - state="disabled", - height=40, - fg_color="gray" - ) - self.result_button.grid(row=0, column=1, padx=10) - - # 🔥 NEW PAUSE BUTTON - self.pause_button = ctk.CTkButton( - self.button_frame, - text="⏸ Pause", - font=("Helvetica", 14, "bold"), - command=self.toggle_pause, - state="disabled", - height=40, - fg_color="#FFA500", - hover_color="#cc8400" - ) - self.pause_button.grid(row=0, column=2, padx=10) - - self.result_label = ctk.CTkLabel(self, - text="", - font=("Helvetica", 18, "italic")) - self.result_label.pack(pady=10) - - # ============================== + # PAUSE BUTTON + self.pause_button = ctk.CTkButton(self.button_frame, + text="Pause", + command=self.toggle_pause, + state="disabled") + self.pause_button.grid(row=0, column=1, padx=10) + + # RESULT BUTTON + self.result_button = ctk.CTkButton(self.button_frame, + text="Check Result", + command=self.check_result, + state="disabled") + self.result_button.grid(row=0, column=2, padx=10) + + # ====================== # START TEST - # ============================== + # ====================== def start_test(self): - feature - if self.timer_id: - self.root.after_cancel(self.timer_id) - - self.timer_running = False - self.elapsed_seconds = 0 - self.timer_label.config(text="⏱ Time: 00:00") - main - self.result_label.config(text="") - self.error_feedback_label.config(text="") - self.current_sentence = random.choice(SENTENCES) - self.sentence_label.config(text=self.current_sentence) - self.input_textbox.delete("1.0", tk.END) - self.input_textbox.focus() - feature - - self.time_left = TEST_DURATION - self.timer_label.config(text=f"Time Left: {self.time_left}s") - self.start_time = time.time() - - self.start_button.config(text="Restart Test", state=tk.DISABLED) - self.result_button.config(state=tk.NORMAL) - self.update_timer() + self.result_label.configure(text="") + self.countdown = 3 + self.input_textbox.configure(state="disabled") + + self.start_button.configure(state="disabled") + + self.show_countdown() + + # ====================== + # COUNTDOWN + # ====================== + def show_countdown(self): + + if self.countdown > 0: + + self.sentence_label.configure( + text=f"Starting in {self.countdown}..." + ) + + self.countdown -= 1 + + self.after(1000, self.show_countdown) - def update_timer(self): - if self.time_left > 0: - self.time_left -= 1 - self.timer_label.config(text=f"Time Left: {self.time_left}s") - self.timer_id = self.root.after(1000, self.update_timer) else: - self.check_result(timeout=True) - self.start_button.config(text="Restart Test", state=tk.NORMAL) - self.result_button.config(state=tk.NORMAL) # Enable result button when test starts - main + self.sentence_label.configure(text="GO!") + + self.after(800, self.begin_test) + + # ====================== + # BEGIN TEST + # ====================== + def begin_test(self): + + self.current_sentence = random.choice(SENTENCES) + + self.sentence_label.configure(text=self.current_sentence) - def enable_button_after_typing(self, event=None): - self.start_button.config(state=tk.NORMAL) - - def validate_typing(self, event=None): - if not self.current_sentence: - return - if not self.timer_running and self.current_sentence: - self.start_time = time.time() - self.elapsed_seconds = 0 - self.timer_label.config(text="⏱ Time: 00:00") - self.timer_running = True - self.update_timer() - typed_text = self.input_textbox.get("1.0", "end-1c") - self.input_textbox.tag_remove("correct", "1.0", "end") - self.input_textbox.tag_remove("incorrect", "1.0", "end") - - has_case_error = False - - for i, ch in enumerate(typed_text): - start = f"1.0+{i}c" - end = f"1.0+{i+1}c" - - if i < len(self.current_sentence): - if ch == self.current_sentence[i]: - self.input_textbox.tag_add("correct", start, end) - else: - self.input_textbox.tag_add("incorrect", start, end) - if ch.lower() == self.current_sentence[i].lower(): - has_case_error = True - - if has_case_error: - self.error_feedback_label.config(text="⚠ Case Mismatch! Check Caps Lock", fg="orange") - elif "incorrect" in self.input_textbox.tag_ranges("incorrect"): - self.error_feedback_label.config(text="❌ Character Error", fg="red") - self.result_label.configure(text="") - self.feedback_label.configure(text="") self.input_textbox.configure(state="normal") self.input_textbox.delete("1.0", "end") + self.input_textbox.focus() - self.current_sentence = random.choice(FALLBACK_SENTENCES) - self.sentence_label.configure(text=self.current_sentence, text_color="white") - - duration = 60 if self.mode == "60s" else 15 - self.time_left = duration + self.start_time = time.time() + self.time_left = TEST_DURATION self.timer_running = True self.paused = False - self.total_paused_time = 0 - self.start_time = time.time() - self.timer_label.configure(text=f"Time Remaining: {duration}s") - self.pause_button.configure(state="normal", text="⏸ Pause") - self.start_button.configure(state="disabled") + self.pause_button.configure(state="normal") self.result_button.configure(state="normal") self.update_timer() - # ============================== + # ====================== # TIMER - # ============================== + # ====================== def update_timer(self): + if not self.timer_running: return - if self.paused: - self.after(1000, self.update_timer) - return + if not self.paused: - if self.time_left > 0: - self.time_left -= 1 - self.timer_label.configure(text=f"Time Remaining: {self.time_left}s") - self.after(1000, self.update_timer) - else: - feature - self.error_feedback_label.config(text="") - - def check_result(self, event=None, timeout=False): - if not self.start_time: - messagebox.showwarning("Warning", "Click 'Start Test' first!") - return "break" - - if self.timer_id: - self.root.after_cancel(self.timer_id) - self.timer_id = None - - if timeout: - elapsed_time = TEST_DURATION # Fix: Use fixed duration for denominator if timer expires - else: - end_time = time.time() - elapsed_time = end_time - self.start_time - + if self.time_left > 0: + + self.timer_label.configure( + text=f"Time Remaining: {self.time_left}s" + ) + + self.time_left -= 1 + + self.after(1000, self.update_timer) - self.check_result() + else: - # ============================== + self.check_result() + + # ====================== # PAUSE / RESUME - # ============================== + # ====================== def toggle_pause(self): + if not self.timer_running: return if not self.paused: - # PAUSE + self.paused = True - self.pause_start = time.time() - self.input_textbox.configure(state="disabled") - self.pause_button.configure(text="▶ Resume") - self.timer_label.configure(text=f"Paused at {self.time_left}s", - text_color="#FFA500") + self.pause_button.configure(text="Resume") + else: - # RESUME - self.paused = False - pause_duration = time.time() - self.pause_start - self.total_paused_time += pause_duration - self.input_textbox.configure(state="normal") - self.input_textbox.focus() - self.pause_button.configure(text="⏸ Pause") - self.timer_label.configure(text=f"Time Remaining: {self.time_left}s", - text_color="#3B8ED0") - - # ============================== - # HANDLE INPUT - # ============================== - def handle_input(self, event): - if not self.current_sentence or self.paused: - return - typed_text = self.input_textbox.get("1.0", "end-1c") - if typed_text.strip() == self.current_sentence: - self.check_result() + self.paused = False + self.pause_button.configure(text="Pause") + self.update_timer() - # ============================== + # ====================== # RESULT - # ============================== - def check_result(self, event=None): + # ====================== + def check_result(self): + if not self.start_time: return self.timer_running = False - self.pause_button.configure(state="disabled") - elapsed_time = (time.time() - self.start_time) - self.total_paused_time - main typed_text = self.input_textbox.get("1.0", "end-1c") - if typed_text.strip() == self.current_sentence: - word_count = len(self.current_sentence.split()) - wpm = (word_count / elapsed_time) * 60 - self.result_label.config(text=f"Well done! Your typing speed is {wpm:.2f} WPM.", fg="green") - self.error_feedback_label.config(text="") - else: - self.result_label.config(text="Incorrect typing! Try again.", fg="red") - self.timer_running = False - self.start_time = None - self.result_button.config(state=tk.DISABLED) - return "break" - def update_timer(self): - if self.timer_running: - self.elapsed_seconds += 1 - minutes = self.elapsed_seconds // 60 - seconds = self.elapsed_seconds % 60 - self.timer_label.config(text=f"⏱ Time: {minutes:02d}:{seconds:02d}") - self.root.after(1000, self.update_timer) - chars_typed = len(typed_text) - wpm = (chars_typed / 5) / (elapsed_time / 60) if elapsed_time > 0 else 0 + elapsed_time = time.time() - self.start_time + + chars = len(typed_text) + + if elapsed_time == 0: + wpm = 0 + else: + wpm = (chars / 5) / (elapsed_time / 60) self.result_label.configure( - text=f"Test Finished! Speed: {wpm:.2f} WPM" + text=f"Typing Speed: {wpm:.2f} WPM" ) self.input_textbox.configure(state="disabled") + self.pause_button.configure(state="disabled") self.start_button.configure(state="normal") - self.start_time = None -if __name__ == "__main__": - feature - root = tk.Tk() - app = TypingSpeedTest(root) - root.mainloop() +if __name__ == "__main__": app = TypingSpeedTest() - main + app.mainloop() \ No newline at end of file