Created
February 6, 2025 18:28
-
-
Save thegamecracks/ee37fe60a559a609f6ee666bb1ca4e1c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import datetime | |
| import textwrap | |
| from tkinter import BooleanVar, Tk | |
| from tkinter.ttk import Checkbutton, Frame, Label, Style | |
| LOREM_IPSUM = "Lorem ipsum odor amet, consectetuer adipiscing elit. Vivamus tincidunt ultricies accumsan feugiat ultrices sagittis tellus? Turpis penatibus convallis; suscipit nisl tincidunt suscipit litora? Ex fusce facilisis ullamcorper aenean iaculis metus pharetra libero. Feugiat fermentum fermentum mauris urna curabitur diam ad. Volutpat parturient arcu nec semper etiam vitae augue convallis dui. Gravida neque lacus donec interdum finibus ullamcorper. Sodales aliquam tempor tempor purus curabitur mauris ridiculus aptent." | |
| class TaskCard(Frame): | |
| MAX_DESCRIPTION_CHARS = 256 | |
| MIN_DESCRIPTION_WIDTH = 300 | |
| def __init__( | |
| self, | |
| parent, | |
| *, | |
| title: str, | |
| description: str, | |
| date: datetime.date, | |
| done: bool, | |
| ) -> None: | |
| super().__init__(parent, borderwidth=1, relief="solid", padding=10) | |
| # Allow our description column to expand horizontally | |
| self.grid_columnconfigure(1, weight=1) | |
| # Use the entire top row for the task title | |
| self.title = Label(self, font="Helvetica 14 bold", text=title) | |
| self.title.grid(row=0, column=0, columnspan=2, sticky="w") | |
| # Use the bottom-left corner for extra info | |
| self.details = TaskDetails(self, date=date, done=done) | |
| self.details.grid(row=1, column=0, padx=(0, 30), sticky="nesw") | |
| # Use the bottom-right corner for description | |
| chars = self.MAX_DESCRIPTION_CHARS | |
| width = self.MIN_DESCRIPTION_WIDTH | |
| description = textwrap.shorten(description, chars, placeholder="...") | |
| self.description = Label(self, text=description, wraplength=width) | |
| self.description.grid(row=1, column=1, sticky="nesw") | |
| # Once this card becomes visible, start wrapping the description text | |
| self.bind("<Map>", lambda event: self._wrap_description()) | |
| def _wrap_description(self) -> None: | |
| if not self.winfo_viewable(): | |
| return | |
| width = self._get_description_width() | |
| self.description.configure(wraplength=width) | |
| # Normally we'd use <Configure> to check if we've been resized, | |
| # but that event doesn't fire when the window is maximized. | |
| # As a hack, we'll wrap the description every 50ms. | |
| self.after(50, self._wrap_description) | |
| def _get_description_width(self) -> int: | |
| bbox = self.grid_bbox(row=1, column=1) or (0, 0, 0, 0) | |
| return max(self.MIN_DESCRIPTION_WIDTH, bbox[2]) | |
| class TaskDetails(Frame): | |
| def __init__( | |
| self, | |
| parent: TaskCard, | |
| *, | |
| date: datetime.date, | |
| done: bool, | |
| ) -> None: | |
| super().__init__(parent) | |
| self.parent = parent | |
| self.date = Label(self, text=date.isoformat()) | |
| self.date.grid(row=0, column=0, sticky="nw") | |
| self.done_var = BooleanVar(self, done) | |
| self.done = Checkbutton(self, variable=self.done_var, text="Completed") | |
| self.done.grid(row=1, column=0, sticky="nw") | |
| self.done_var.trace_add("write", self._on_done_changed) | |
| def _on_done_changed(self, name1: str, name2: str, op: str) -> None: | |
| # https://www.tcl-lang.org/man/tcl8.6/TclCmd/trace.htm#M14 | |
| title = self.parent.title["text"] | |
| done = "done" if self.done_var.get() else "not done" | |
| print(f"Task {title!r} marked as {done}") | |
| def main() -> None: | |
| app = Tk() | |
| app.geometry("680x920") # description wrapping can go nuts if we don't set a size | |
| style = Style(app) | |
| style.configure(".", background="white") | |
| # Put everything inside a themed frame for our background | |
| content = Frame(app, padding=30) | |
| content.pack(expand=True, fill="both") | |
| # Expand cards horizontally, and also expand the top card vertically | |
| content.grid_columnconfigure(0, weight=1) | |
| content.grid_rowconfigure(0, minsize=160, weight=1) | |
| for i in range(5): | |
| card = TaskCard( | |
| content, | |
| title=f"Title of task {i+1}", | |
| date=datetime.date(2025, 2, 5 + i), | |
| description=LOREM_IPSUM, | |
| done=i % 2 == 1, | |
| ) | |
| card.grid(pady=(0, 10), sticky="nesw") | |
| app.mainloop() | |
| if __name__ == "__main__": | |
| main() |
Author
thegamecracks
commented
Feb 6, 2025

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment