First Project – Calendar App

Intent

For this project I made a Calendar App where you can add events and view your event schedule for the day. These events can also be repeating.

This project is meant as a tool to help with time-management and planning.

Code Planning

Part One: Save Event Format

  1. Events are listed in increasing end time order.
  2. Event Format
    • -Completeness
    • -EndTime
    • -StartTime
    • -StepTime
    • -Description
    • #

Example Save File

-0
-1759302000
-1756710000
-86400
-Daily 7am reminder 
#

Part Two: Load Event Format

  1. Each event will be stored as a list
    • [end time, start time, step amt, name, desc, finished]
  2. Multiple events will be stored in a master list and sorted by increasing end time.

Part Three: Get Events on a Specific Day

Returns a list of event indices and event times

  1. First Check: Did this group of events already end
  2. Second Check: Did this group of events start yet
  3. Third Check: Is there an event TODAY.
    • Let k be the smallest integer where
      • Start + k * Step > Date
    • The event is TODAY if
      • Start + k * step < Date + 86400
  4. Calculate the time of the event on that day
    • Event Time = Start + k * step – Date
  5. Add tuple to a list
    • (Event Index, Event Time)
  6. Return this list

Part Four: Get and Update Inputs of Mouse and Keyboard

  1. Save Inputs from Previous Frame
  2. Save Inputs from Current Frame
  3. Save Mouse Position

Part Five: Query Inputs of Mouse and Keyboard

  1. Parameters: Mode (String), Key (id)
    • Key identifies which specific input we are querying.
  2. “down” Mode: Return if the specific input is on
  3. “press” Mode: Return if the specific input was turned on this frame
  4. “release” Mode: Return if the specific input was turned off this frame

Part Six: Add Event

  1. Get Inputs: Start Time, End Time, Step, Name, and Description
  2. Insert Event into Events list while maintaining sorted by end time. (USE LOAD EVENT FORMAT)

Part Seven: Remove Event

  1. Delete event from events list.

Note: All times will be saved using seconds since epoch.

Flowchart

The following flowchart shows the structure and logic of how the above is implemented.

Flowchart

Code

File Loader Class
To save and load events and get events on a specific day. (See parts One to Three)
class CalendarLoader:
    def __init__(self, filename):
        self.events = []
        self.filename = filename

    def set_file(self, filename):
        self.filename = filename

    def load_events(self, filepath=None):
        if filepath is None:
            filepath = self.filename
        self.events = []
        infile = open(filepath, "r")
        lines = [] # all the lines between previous and next "#" markings
        for line in infile:
            if line[0] == '#':
                # gets event settings based on above format 
                b = int(lines[0])
                end = int(lines[1])
                start = int(lines[2])
                step = int(lines[3])
                name = lines[4]
                desc = "\n".join(lines[5:])
                self.events.append([end, start, step, name, desc, b])
                lines = [] # reset lines 
            else:
                # add line to lines list. 
                lines.append(line[1:-1])
        infile.close()

    def save_events(self, filepath=None):
        if filepath is None:
            filepath = self.filename
        s = ""
        for event in self.events:
            # Saves each event based on the above format 
            s += f"-{int(event[5])}\n"
            s += f"-{int(event[0])}\n"
            s += f"-{int(event[1])}\n"
            s += f"-{int(event[2])}\n"
            s += f"-{event[3]}\n"
            s += f"-{event[4]}\n"
            s += "#\n"
        outfile = open(filepath, "w")
        outfile.write(s)
        outfile.close()

    def search(self, time):
        # Basic Binary Search
        low = -1
        high = len(self.events) - 1
        while low < high:
            mid = low + (high - low + 1) // 2
            if self.events[mid][0] <= time:
                low = mid
            else:
                high = mid - 1
        return low + 1

    def getDay(self, date):
        # Follows the above Instructions to get all the events that happen on that day. 
        events = []
        for i in range(len(self.events) - 1, -1, -1):
            event = self.events[i]
            if event[0] < date:
                break
            k = (date - event[1] - 1) // event[2] + 1
            t = event[1] + k * event[2] - date
            if k >= 0 and t < 86400:
                events.append((t, i))
        return events

    def getEvent(self, idx):
        return self.events[idx]
Pygame Input Handler
To handle updating and querying inputs from the mouse and keyboard. (See parts Four to Five)
import pygame

class InputHandler:
    # Get Modes: "down", "press", and "release" 
    def __init__(self):
        self.keyDown = pygame.key.get_pressed()
        self.prevKeyDown = pygame.key.get_pressed()
        self.mouseDown = pygame.mouse.get_pressed(3)
        self.prevMouseDown = pygame.mouse.get_pressed(3)
        self.mousePos = pygame.mouse.get_pos()

    def update(self):
        # Set Previous Mouse and Key Downs 
        self.prevKeyDown = self.keyDown
        self.prevMouseDown = self.mouseDown
        # Set Current Mouse and Key Downs 
        self.keyDown = pygame.key.get_pressed()
        self.mouseDown = pygame.mouse.get_pressed(3)
        self.mousePos = pygame.mouse.get_pos()

    def getKey(self, key, mode="down"):
        match mode:
            case "down": # Is Key Down 
                return self.keyDown[key]
            case "press": # Was Key Pressed this frame 
                return self.keyDown[key] and not self.prevKeyDown[key]
            case "release": Was Key Released this frame 
                return self.prevKeyDown[key] and not self.keyDown[key]
            case _:
                print(f"Unknown Mode {mode}")
                return False

    def getMouse(self, mouse, mode="down"):
        match mode:
            case "down": # Is Mouse Button Down 
                return self.mouseDown[mouse]
            case "press": # Was Mouse Button Pressed this frame 
                return self.mouseDown[mouse] and not self.prevMouseDown[mouse]
            case "release": # Was Mouse Button Release this frame 
                return self.prevMouseDown[mouse] and not self.mouseDown[mouse]
            case _:
                print(f"Unknown Mode {mode}")
                return False
Primary Calendar Class
To Add and Remove events. (See parts Six to Seven)
import datetime
import time
import pygame
from CalendarLoader import *

utc = datetime.timezone.utc

class Calendar:
    def __init__(self, filename):
        self.loader = CalendarLoader(filename)
        self.loader.load_events()
        # Floor "now" to 00:00 on the same day 
        now = (time.time() // 86400) * 86400
        self.line = 20
        self.date = now
        self.start = now
        self.end = now
        self.step = 86400
        self.addIdx = 0
        self.selected = 0

    def set_filename(self, filename):
        self.loader.set_file(filename)

    def render(self, font, inHand, offset):
        # Get all the events on the current date 
        events = sorted(self.loader.getDay(self.date))
        relx, rely = inHand.mousePos[0] - offset[0], inHand.mousePos[1] - offset[1]
        idx = -1
        if 20 < relx < 250 and 5 + 1.5 * self.line < rely:
            idx = int((rely - 5) / self.line - 1.5)
        if idx >= len(events):
            idx = -1
        if inHand.getMouse(0, "press") and idx != -1:
            self.selected = idx # Check if any events where clicked 
        if inHand.getKey(pygame.K_UP, "press"):
            self.addIdx = (self.addIdx - 1 + 3) % 3 # decrease add index 
        if inHand.getKey(pygame.K_DOWN, "press"):
            self.addIdx = (self.addIdx + 1) % 3 # increase add index 

        # Calendar Window 
        surf = pygame.Surface((600, 400))
        surf.fill((0, 255, 255))
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.date, utc).strftime("%A %B %d, %Y"), True, (0, 0, 0)), (20, 5))
        for i, (t, eventIdx) in enumerate(events):
            event = self.loader.getEvent(eventIdx)
            timing = datetime.datetime.fromtimestamp(t, utc).strftime("%I:%M %p")
            colour = ((0, 0, 0) if event[5] != 1 else (0, 180, 0)) if i != self.selected else (0, 0, 127)
            status = "[]" if event[5] == 1 else "X"
            surf.blit(font.render(f"{status} {timing}: {event[3]}", True, colour), (20, 5 + (i + 1.5) * self.line))
            if i == self.selected:
                surf.blit(font.render(datetime.datetime.fromtimestamp(event[1], utc).strftime("Start: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 9 * self.line))
                surf.blit(font.render(datetime.datetime.fromtimestamp(event[0], utc).strftime("End: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 10 * self.line))
                surf.blit(font.render(datetime.datetime.fromtimestamp(event[2] - 86400, utc).strftime("Step: %d days; %H hours"), True, (0, 0, 0)), (270, 5 + 11 * self.line))
                surf.blit(font.render("=== Description ===", True, (0, 0, 0)), (270, 5 + 12 * self.line))
                surf.blit(font.render(f"{event[4]}", True, (0, 0, 0)), (270, 5 + 13 * self.line))
        if inHand.getKey(pygame.K_f, "press") and 0 <= self.selected < len(events):
            self.loader.events[events[self.selected][1]][5] = 1 - self.loader.events[events[self.selected][1]][5]
        colours = [(0, 0, 0), (0, 0, 0), (0, 0, 0)]
        colours[self.addIdx] = (0, 0, 127)
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.start, utc).strftime("Start: %A %B %d, %Y - %H"), True, colours[0]), (270, 5))
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.end, utc).strftime("End: %A %B %d, %Y - %H"), True, colours[1]), (270, 5 + self.line))
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.step - 86400, utc).strftime("Step: %d days; %H hours"), True, colours[2]), (270, 5 + 2 * self.line))
        surf.blit(font.render(("START", "END", "STEP")[self.addIdx], True, (0, 127, 0)), (300, 5 + 4 * self.line))
        clickIdx = (-1, -1)
        for t in range(2):
            for i in range((4, 4, 2)[self.addIdx]):
                bx = 370 + i * 25
                by = 5 + 4 * self.line + t * 25
                pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                symbol = "+" if t == 0 else "-"
                surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
                if abs(relx - bx) < 10 and abs(rely - by) < 10:
                    clickIdx = (i, t)
        if inHand.getMouse(0, "press") and clickIdx[0] != -1:
            m = (1, -1)[clickIdx[1]]
            i = clickIdx[0]
            match self.addIdx:
                case 0:
                    if i == 1:
                        self.start += m * 86400
                    else:
                        dt = datetime.datetime.fromtimestamp(self.start, utc)
                        para = [dt.month, dt.day, dt.year, dt.hour]
                        if i == 0:
                            para[i] = (para[i] + m + 11) % 12 + 1
                        elif i == 3:
                            para[i] = (para[i] + m + 24) % 24
                        else:
                            para[i] += m
                        self.start = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                case 1:
                    if i == 1:
                        self.end += m * 86400
                    else:
                        dt = datetime.datetime.fromtimestamp(self.end, utc)
                        para = [dt.month, dt.day, dt.year, dt.hour]
                        if i == 0:
                            para[i] = (para[i] + m + 11) % 12 + 1
                        elif i == 3:
                            para[i] = (para[i] + m + 24) % 24
                        else:
                            para[i] += m
                        self.end = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                case 2:
                    self.step += m * (86400, 3600)[i]
            self.start = max(self.start, 0)
            self.step = max(self.step, 86400)
            self.end = max(self.end, 0)

        clickIdx = -1
        surf.blit(font.render("Events: Add    Remove    Save", True, (0, 0, 0)), (270, 7 * self.line - 8))
        for t in range(3):
            bx = 340 + t * 55
            by = 5 + 8 * self.line
            pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
            symbol = "+-S"[t]
            o = 2 if t == 2 else 0
            surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3 - o, by - 13 + o))
            if abs(relx - bx) < 10 and abs(rely - by) < 10:
                clickIdx = t
        if inHand.getMouse(0, "press") and clickIdx != -1:
            if clickIdx == 0:
                print("Name Of Event: ")
                name = input()
                desc = ""
                print("Description: ")
                while True:
                    add = input()
                    if add == "":
                        break
                    desc += add + '\n'
                index = self.loader.search(time.time() - 2073600)
                self.loader.events = self.loader.events[index:]
                self.loader.events.insert(self.loader.search(self.end), [self.end, self.start, self.step, name, desc[:-1], 0])
            elif clickIdx == 1:
                if 0 <= self.selected < len(events):
                    self.loader.events.pop(events[self.selected][1])
            elif clickIdx == 2:
                self.loader.save_events()

        return surf
Pygame Main Loop
To integrate all parts.
import pygame
from InputHandler import *
from Calendar import *

# Basic Pygame Setup
pygame.init()
pygame.font.init()
high_score = 0
score = 0
fps = 60
fpsClock = pygame.time.Clock()

width, height = 840, 480
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Calendar App")

font = pygame.font.SysFont("comicsans", 25)
inHand = InputHandler()
calendar = Calendar("event.txt")

# print(calendar.loader.events)

# Main Game Loop
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            running = False

    if not running:
        break
    inHand.update()

    # White Background 
    screen.fill((255, 255, 255))

    # Handles Click Detection for the Calendar Date Controller 
    clickIdx = (-1, -1)
    screen.blit(font.render("Calendar Date Controller", True, (0, 0, 0)), (630, 18))
    for i in range(3):
        by = 55 + i * 25
        label = ("Year", "Month", "Day")[i]
        screen.blit(font.render(f"{label}: ", True, (0, 0, 0)), (630, by - 12))
        for t in range(2):
            bx = 700 + t * 25
            pygame.draw.rect(screen, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
            symbol = "+" if t == 0 else "-"
            screen.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
            if abs(inHand.mousePos[0] - bx) < 10 and abs(inHand.mousePos[1] - by) < 10:
                clickIdx = (i, t)
    if inHand.getMouse(0, "press") and clickIdx[0] != -1:
        m = (1, -1)[clickIdx[1]]
        i = clickIdx[0]
        if i == 2:
            calendar.date += m * 86400
        else:
            dt = datetime.datetime.fromtimestamp(calendar.date, utc)
            para = [dt.year, dt.month, dt.day]
            if i == 1:
                para[i] = (para[i] + m + 11) % 12 + 1
            else:
                para[i] += m
            calendar.date = datetime.datetime(para[0], para[1], para[2], tzinfo=utc).timestamp()
        calendar.date = max(calendar.date, 0)

    screen.blit(calendar.render(font, inHand, (20, 20)), (20, 20))

    pygame.display.flip()
    fpsClock.tick(fps)

# Closed the game
print("App Closed Successfully")
Single-File Full Code
import time
import pygame
import datetime

# ============ FILE LOADER ============ 

class CalendarLoader:
    def __init__(self, filename):
        self.events = []
        self.filename = filename

    def set_file(self, filename):
        self.filename = filename

    def load_events(self, filepath=None):
        if filepath is None:
            filepath = self.filename
        self.events = []
        infile = open(filepath, "r")
        lines = []
        for line in infile:
            if line[0] == '#':
                b = int(lines[0])
                end = int(lines[1])
                start = int(lines[2])
                step = int(lines[3])
                name = lines[4]
                desc = "\n".join(lines[5:])
                self.events.append([end, start, step, name, desc, b])
                lines = []
            else:
                lines.append(line[1:-1])
        infile.close()

    def save_events(self, filepath=None):
        if filepath is None:
            filepath = self.filename
        s = ""
        for event in self.events:
            s += f"-{int(event[5])}\n"
            s += f"-{int(event[0])}\n"
            s += f"-{int(event[1])}\n"
            s += f"-{int(event[2])}\n"
            s += f"-{event[3]}\n"
            s += f"-{event[4]}\n"
            s += "#\n"
        outfile = open(filepath, "w")
        outfile.write(s)
        outfile.close()

    def search(self, time):
        # Basic Binary Search
        low = -1
        high = len(self.events) - 1
        while low < high:
            mid = low + (high - low + 1) // 2
            if self.events[mid][0] <= time:
                low = mid
            else:
                high = mid - 1
        return low + 1

    def getDay(self, date):
        # Follows the above Instructions to get all the events that happen on that day. 
        events = []
        for i in range(len(self.events) - 1, -1, -1):
            event = self.events[i]
            if event[0] < date:
                break
            k = (date - event[1] - 1) // event[2] + 1
            t = event[1] + k * event[2] - date
            if k >= 0 and t < 86400:
                events.append((t, i))
        return events

    def getEvent(self, idx):
        return self.events[idx]

# ============ PYGAME INPUT HANDLER ============ 

class InputHandler:
    def __init__(self):
        self.keyDown = pygame.key.get_pressed()
        self.prevKeyDown = pygame.key.get_pressed()
        self.mouseDown = pygame.mouse.get_pressed(3)
        self.prevMouseDown = pygame.mouse.get_pressed(3)
        self.mousePos = pygame.mouse.get_pos()

    def update(self):
        self.prevKeyDown = self.keyDown
        self.prevMouseDown = self.mouseDown
        self.keyDown = pygame.key.get_pressed()
        self.mouseDown = pygame.mouse.get_pressed(3)
        self.mousePos = pygame.mouse.get_pos()

    def getKey(self, key, mode="down"):
        match mode:
            case "down":
                return self.keyDown[key]
            case "press":
                return self.keyDown[key] and not self.prevKeyDown[key]
            case "release":
                return self.prevKeyDown[key] and not self.keyDown[key]
            case _:
                print(f"Unknown Mode {mode}")
                return False

    def getMouse(self, mouse, mode="down"):
        match mode:
            case "down":
                return self.mouseDown[mouse]
            case "press":
                return self.mouseDown[mouse] and not self.prevMouseDown[mouse]
            case "release":
                return self.prevMouseDown[mouse] and not self.mouseDown[mouse]
            case _:
                print(f"Unknown Mode {mode}")
                return False

# ============ CALENDAR CLASS ============ 

utc = datetime.timezone.utc

class Calendar:
    def __init__(self, filename):
        self.loader = CalendarLoader(filename)
        self.loader.load_events()
        now = (time.time() // 86400) * 86400
        self.line = 20
        self.date = now
        self.start = now
        self.end = now
        self.step = 86400
        self.addIdx = 0
        self.selected = 0

    def set_filename(self, filename):
        self.loader.set_file(filename)

    def render(self, font, inHand, offset):
        events = sorted(self.loader.getDay(self.date))
        relx, rely = inHand.mousePos[0] - offset[0], inHand.mousePos[1] - offset[1]
        idx = -1
        if 20 < relx < 250 and 5 + 1.5 * self.line < rely:
            idx = int((rely - 5) / self.line - 1.5)
        if idx >= len(events):
            idx = -1
        if inHand.getMouse(0, "press") and idx != -1:
            self.selected = idx
        if inHand.getKey(pygame.K_UP, "press"):
            self.addIdx = (self.addIdx - 1 + 3) % 3
        if inHand.getKey(pygame.K_DOWN, "press"):
            self.addIdx = (self.addIdx + 1) % 3

        surf = pygame.Surface((600, 400))
        surf.fill((0, 255, 255))
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.date, utc).strftime("%A %B %d, %Y"), True, (0, 0, 0)), (20, 5))
        for i, (t, eventIdx) in enumerate(events):
            event = self.loader.getEvent(eventIdx)
            timing = datetime.datetime.fromtimestamp(t, utc).strftime("%I:%M %p")
            colour = ((0, 0, 0) if event[5] != 1 else (0, 180, 0)) if i != self.selected else (0, 0, 127)
            status = "[]" if event[5] == 1 else "X"
            surf.blit(font.render(f"{status} {timing}: {event[3]}", True, colour), (20, 5 + (i + 1.5) * self.line))
            if i == self.selected:
                surf.blit(font.render(datetime.datetime.fromtimestamp(event[1], utc).strftime("Start: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 9 * self.line))
                surf.blit(font.render(datetime.datetime.fromtimestamp(event[0], utc).strftime("End: %A %B %d, %Y - %H"), True, (0, 0, 0)), (270, 5 + 10 * self.line))
                surf.blit(font.render(datetime.datetime.fromtimestamp(event[2] - 86400, utc).strftime("Step: %d days; %H hours"), True, (0, 0, 0)), (270, 5 + 11 * self.line))
                surf.blit(font.render("=== Description ===", True, (0, 0, 0)), (270, 5 + 12 * self.line))
                surf.blit(font.render(f"{event[4]}", True, (0, 0, 0)), (270, 5 + 13 * self.line))
        if inHand.getKey(pygame.K_f, "press"):
            self.loader.events[events[self.selected][1]][5] = 1 - self.loader.events[events[self.selected][1]][5]
        colours = [(0, 0, 0), (0, 0, 0), (0, 0, 0)]
        colours[self.addIdx] = (0, 0, 127)
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.start, utc).strftime("Start: %A %B %d, %Y - %H"), True, colours[0]), (270, 5))
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.end, utc).strftime("End: %A %B %d, %Y - %H"), True, colours[1]), (270, 5 + self.line))
        surf.blit(font.render(datetime.datetime.fromtimestamp(self.step - 86400, utc).strftime("Step: %d days; %H hours"), True, colours[2]), (270, 5 + 2 * self.line))
        surf.blit(font.render(("START", "END", "STEP")[self.addIdx], True, (0, 127, 0)), (300, 5 + 4 * self.line))
        clickIdx = (-1, -1)
        for t in range(2):
            for i in range((4, 4, 2)[self.addIdx]):
                bx = 370 + i * 25
                by = 5 + 4 * self.line + t * 25
                pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
                symbol = "+" if t == 0 else "-"
                surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
                if abs(relx - bx) < 10 and abs(rely - by) < 10:
                    clickIdx = (i, t)
        if inHand.getMouse(0, "press") and clickIdx[0] != -1:
            m = (1, -1)[clickIdx[1]]
            i = clickIdx[0]
            match self.addIdx:
                case 0:
                    if i == 1:
                        self.start += m * 86400
                    else:
                        dt = datetime.datetime.fromtimestamp(self.start, utc)
                        para = [dt.month, dt.day, dt.year, dt.hour]
                        if i == 0:
                            para[i] = (para[i] + m + 11) % 12 + 1
                        elif i == 3:
                            para[i] = (para[i] + m + 24) % 24
                        else:
                            para[i] += m
                        self.start = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                case 1:
                    if i == 1:
                        self.end += m * 86400
                    else:
                        dt = datetime.datetime.fromtimestamp(self.end, utc)
                        para = [dt.month, dt.day, dt.year, dt.hour]
                        if i == 0:
                            para[i] = (para[i] + m + 11) % 12 + 1
                        elif i == 3:
                            para[i] = (para[i] + m + 24) % 24
                        else:
                            para[i] += m
                        self.end = datetime.datetime(para[2], para[0], para[1], para[3], tzinfo=utc).timestamp()
                case 2:
                    self.step += m * (86400, 3600)[i]
            self.start = max(self.start, 0)
            self.step = max(self.step, 86400)
            self.end = max(self.end, 0)

        clickIdx = -1
        surf.blit(font.render("Events: Add    Remove    Save", True, (0, 0, 0)), (270, 7 * self.line - 8))
        for t in range(3):
            bx = 340 + t * 55
            by = 5 + 8 * self.line
            pygame.draw.rect(surf, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
            symbol = "+-S"[t]
            o = 2 if t == 2 else 0
            surf.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3 - o, by - 13 + o))
            if abs(relx - bx) < 10 and abs(rely - by) < 10:
                clickIdx = t
        if inHand.getMouse(0, "press") and clickIdx != -1:
            if clickIdx == 0:
                print("Name Of Event: ")
                name = input()
                desc = ""
                print("Description: ")
                while True:
                    add = input()
                    if add == "":
                        break
                    desc += add + '\n'
                index = self.loader.search(time.time() - 2073600)
                self.loader.events = self.loader.events[index:]
                self.loader.events.insert(self.loader.search(self.end), [self.end, self.start, self.step, name, desc[:-1], 0])
            elif clickIdx == 1:
                if 0 <= self.selected < len(events):
                    self.loader.events.pop(events[self.selected][1])
            elif clickIdx == 2:
                self.loader.save_events()

        return surf

# ============ PRIMARY PYGAME LOOP ============ 

pygame.init()
pygame.font.init()
high_score = 0
score = 0
fps = 60
fpsClock = pygame.time.Clock()

width, height = 840, 480
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Calendar App")

font = pygame.font.SysFont("comicsans", 25)
inHand = InputHandler()
calendar = Calendar("event.txt")

# print(calendar.loader.events)

# Main Game Loop
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            running = False

    if not running:
        break
    inHand.update()

    screen.fill((255, 255, 255))

    clickIdx = (-1, -1)
    screen.blit(font.render("Calendar Date Controller", True, (0, 0, 0)), (630, 18))
    for i in range(3):
        by = 55 + i * 25
        label = ("Year", "Month", "Day")[i]
        screen.blit(font.render(f"{label}: ", True, (0, 0, 0)), (630, by - 12))
        for t in range(2):
            bx = 700 + t * 25
            pygame.draw.rect(screen, (0, 0, 0), (bx - 10, by - 10, 20, 20), 2, 4)
            symbol = "+" if t == 0 else "-"
            screen.blit(font.render(symbol, True, (0, 0, 0)), (bx - 3, by - 13))
            if abs(inHand.mousePos[0] - bx) < 10 and abs(inHand.mousePos[1] - by) < 10:
                clickIdx = (i, t)
    if inHand.getMouse(0, "press") and clickIdx[0] != -1:
        m = (1, -1)[clickIdx[1]]
        i = clickIdx[0]
        if i == 2:
            calendar.date += m * 86400
        else:
            dt = datetime.datetime.fromtimestamp(calendar.date, utc)
            para = [dt.year, dt.month, dt.day]
            if i == 1:
                para[i] = (para[i] + m + 11) % 12 + 1
            else:
                para[i] += m
            calendar.date = datetime.datetime(para[0], para[1], para[2], tzinfo=utc).timestamp()
        calendar.date = max(calendar.date, 0)

    screen.blit(calendar.render(font, inHand, (20, 20)), (20, 20))

    pygame.display.flip()
    fpsClock.tick(fps)

# Closed the game
print("App Closed Successfully")

How to use

How To Run
  1. Open trinket.io/pygame
  2. Paste code from “Single File Full Code” into editor
  3. Create new file called “event.txt”
  4. Run the code
App Manual

Add Event

  1. Set Start, End, and Step (Using Up/Down Arrow Keys and on-screen [+][-] buttons)
  2. Use Add [+] Button
  3. Fill in the name and description in python console.

Remove Event

  1. Click on Event from the left view.
  2. Use Remove [-] Button

Change View Date

  1. Use the right “Calendar Date Controller” to increment or decrement the months, year, or day of viewing.

Save Changes

  1. Use Save [S] Button

View More Details

  1. Click on Event from the left view.
  2. A more detailed description of the event will appear in the middle right of the cyan part.

Mark/Unmark Event

  1. Click on Event from the left view.
  2. Press “f” to toggle Finished/Unfinished
Demo

https://drive.google.com/file/d/188g953LIu57BodflmnT3jkoBiPxKbs2b/view

AI usage

I did not use AI for this assignment.

Comments

2 Responses to “First Project – Calendar App”

  1. mcrompton Avatar
    mcrompton

    Good start, Aston. Two things are needed. A real flowchart, and some easily runable version of the code or video demo. A reader shouldn’t have to spend time downloading libraries into software simply to run your code. Can we get it to demo, “out of the box?” Also, I either need a statement than you didn’t use AI at all in this assignment, or I need to see the transcript. Please add these elements and resubmit. Thanks!

  2. mcrompton Avatar
    mcrompton

    Thank you, Aston. This is much more complete. You seem to have a more advanced knowledge of programming and have created a complex program to demonstrate this. The video walk through offers a clear demonstration of the intent of the program. What I am still missing is an explanation of the code. You have broken the code into sections and given each section a label, but don’t actually explain how each section works! Could you please add that?

    For future consideration, you might want to think about the UX (user experience). I found the program awkward to learn until I’d watched the video. A good UI would make the use of the program intuitive and any explanation redundant. Simple things like function headings within the UI could more clearly identify the function of each section. Not the point of this particular assignment, so I’m not asking you to change anything, but you might want to think about this in future work.

Leave a Reply to mcrompton Cancel reply

Your email address will not be published. Required fields are marked *