From 27e754b3f79f570ce36adda5d9647b8dd5f3e590 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= <mordae@anilinux.org>
Date: Sat, 8 Mar 2025 23:34:19 +0100
Subject: [PATCH] Add space in front of files

---
 lazy_player/__init__.py   | 84 ++++++++++++++++++++++++---------------
 lazy_player/file_model.py | 43 ++++++++++++++++++++
 lazy_player/style.css     |  8 +++-
 3 files changed, 103 insertions(+), 32 deletions(-)
 create mode 100644 lazy_player/file_model.py

diff --git a/lazy_player/__init__.py b/lazy_player/__init__.py
index 68645e1..f089dbc 100644
--- a/lazy_player/__init__.py
+++ b/lazy_player/__init__.py
@@ -7,18 +7,20 @@ from typing import Any, cast
 
 import gi
 
+from .file_model import FileItem, FileType
+
 gi.require_version("Gdk", "4.0")
 gi.require_version("Gtk", "4.0")
 gi.require_version("Gst", "1.0")
 gi.require_version("Pango", "1.0")
-from gi.repository import Gdk, Gst, Gtk, Pango  # NOQA: E402
+from gi.repository import Gdk, Gio, Gst, Gtk, Pango  # NOQA: E402
 
 
 class MainWindow(Gtk.ApplicationWindow):
     file_info_label: Gtk.Label
     stack: Gtk.Stack
     list_view: Gtk.ListView
-    list_store: Gtk.StringList
+    list_store: Gio.ListStore
     video_widget: Gtk.Picture
     pipeline: Gst.Pipeline
     overlay_tick_callback_id: int
@@ -127,7 +129,7 @@ class MainWindow(Gtk.ApplicationWindow):
         main_box.append(grid)
 
         # Create list store and view
-        self.list_store = Gtk.StringList()
+        self.list_store = Gio.ListStore(item_type=FileItem)
         self.list_view = Gtk.ListView()
         selection_model = Gtk.SingleSelection.new(self.list_store)
         selection_model.connect("selection-changed", self._on_selection_changed)
@@ -137,21 +139,20 @@ class MainWindow(Gtk.ApplicationWindow):
         def on_activate(widget: Gtk.ListView, index: int):
             selected_item = selection_model.get_item(index)
             if selected_item:
-                string_obj = cast(Gtk.StringObject, selected_item)
-                string = string_obj.get_string()
+                file_item = cast(FileItem, selected_item)
 
-                if string.endswith("/"):
-                    os.chdir(string)
+                if file_item.file_type == FileType.DIRECTORY:
+                    os.chdir(file_item.full_path)
                     self._populate_file_list()
                     return
 
                 # Start playing the video
                 playbin = self.pipeline.get_by_name("playbin")
                 if playbin:
-                    playbin.set_property("uri", f"file://{os.path.abspath(string)}")
+                    playbin.set_property("uri", f"file://{os.path.abspath(file_item.full_path)}")
                     self.pipeline.set_state(Gst.State.PLAYING)
                     self.stack.set_visible_child_name("overlay")
-                    self.show_overlay_text(string)
+                    self.show_overlay_text(f"Playing: {file_item.name}")
 
         self.list_view.connect("activate", on_activate)
 
@@ -172,14 +173,31 @@ class MainWindow(Gtk.ApplicationWindow):
         self.set_child(self.stack)
 
     def _setup_list_item(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem):
+        # Create horizontal box to hold icon and label
+        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+        box.set_spacing(8)
+
+        # Create icon placeholder
+        icon = Gtk.Box()
+        icon.set_css_classes(["file-icon"])
+        box.append(icon)
+
+        # Create label
         label = Gtk.Label()
         label.set_halign(Gtk.Align.START)
-        list_item.set_child(label)
+        box.append(label)
+
+        list_item.set_child(box)
 
     def _bind_list_item(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem):
-        label = cast(Gtk.Label, list_item.get_child())
-        item = cast(Gtk.StringObject, list_item.get_item())
-        label.set_text(item.get_string())
+        box = cast(Gtk.Box, list_item.get_child())
+        icon = cast(Gtk.Box, box.get_first_child())
+        label = cast(Gtk.Label, box.get_last_child())
+        item = cast(FileItem, list_item.get_item())
+
+        # Make icon transparent for directories
+        icon.set_opacity(0.0 if item.file_type == FileType.DIRECTORY else 1.0)
+        label.set_text(item.name)
 
     def _on_selection_changed(
         self,
@@ -192,8 +210,8 @@ class MainWindow(Gtk.ApplicationWindow):
         else:
             selected_item = selection_model.get_selected_item()
             if selected_item:
-                string_obj = cast(Gtk.StringObject, selected_item)
-                self.file_info_label.set_text(string_obj.get_string())
+                file_item = cast(FileItem, selected_item)
+                self.file_info_label.set_text(file_item.full_path)
 
     def _toggle_play_pause(self) -> None:
         """Toggle between play and pause states"""
@@ -343,31 +361,35 @@ class MainWindow(Gtk.ApplicationWindow):
     def _populate_file_list(self) -> None:
         # TODO: Implement proper version sort (strverscmp equivalent)
 
-        directories: list[str] = ["../"]
-        files: list[str] = []
+        items: list[FileItem] = []
+
+        # Add parent directory
+        items.append(FileItem("..", FileType.DIRECTORY, "../"))
 
         with os.scandir(".") as it:
             for entry in it:
-                if entry.name == ".." or not entry.name.startswith("."):
-                    parts = entry.name.split(".")
-                    suffix = parts[-1] if len(parts) >= 2 else ""
+                if entry.name != ".." and not entry.name.startswith("."):
+                    try:
+                        if entry.is_dir():
+                            items.append(FileItem(entry.name, FileType.DIRECTORY, entry.name + "/"))
+                        else:
+                            file_item = FileItem.from_path(entry.name)
+                            items.append(file_item)
+                    except ValueError:
+                        # Skip unsupported file types
+                        continue
 
-                    if entry.is_dir():
-                        directories.append(entry.name + "/")
-                    elif suffix in ("mkv", "mp4", "avi"):
-                        files.append(entry.name)
-
-        directories.sort(key=lambda x: x.lower())
-        files.sort(key=lambda x: x.lower())
+        # Sort directories first, then files, both alphabetically
+        items.sort(key=lambda x: (x.file_type != FileType.DIRECTORY, x.name.lower()))
 
         while self.list_store.get_n_items():
             self.list_store.remove(0)
 
-        for name in directories + files:
-            self.list_store.append(name)
+        for item in items:
+            self.list_store.append(item)
 
-        all = directories + files
-        self.file_info_label.set_text(all[0] if all else "")
+        if items:
+            self.file_info_label.set_text(items[0].full_path)
 
 
 class App(Gtk.Application):
diff --git a/lazy_player/file_model.py b/lazy_player/file_model.py
new file mode 100644
index 0000000..f7f6b6b
--- /dev/null
+++ b/lazy_player/file_model.py
@@ -0,0 +1,43 @@
+from __future__ import annotations
+
+import os
+from enum import Enum, auto
+
+import gi
+
+gi.require_version("GObject", "2.0")
+from gi.repository import GObject  # NOQA: E402
+
+
+class FileType(Enum):
+    DIRECTORY = auto()
+    VIDEO = auto()
+
+
+class FileItem(GObject.Object):
+    __gtype_name__ = "FileItem"
+
+    def __init__(
+        self,
+        name: str,
+        file_type: FileType,
+        full_path: str,
+    ):
+        super().__init__()
+        self.name = name
+        self.file_type = file_type
+        self.full_path = full_path
+
+    @staticmethod
+    def from_path(path: str) -> FileItem:
+        name = os.path.basename(path)
+        if path.endswith("/"):
+            return FileItem(name, FileType.DIRECTORY, path)
+
+        parts = name.split(".")
+        suffix = parts[-1].lower() if len(parts) >= 2 else ""
+
+        if suffix in ("mkv", "mp4", "avi"):
+            return FileItem(name, FileType.VIDEO, path)
+
+        raise ValueError(f"Unsupported file type: {path}")
diff --git a/lazy_player/style.css b/lazy_player/style.css
index 51e6b3d..db079dd 100644
--- a/lazy_player/style.css
+++ b/lazy_player/style.css
@@ -4,6 +4,13 @@ listview > row {
   font-family: monospace;
 }
 
+.file-icon {
+  min-width: 32px;
+  min-height: 32px;
+  margin-right: 8px;
+  border: 1px solid #666;
+}
+
 #black-overlay {
   background-color: black;
 }
@@ -16,5 +23,4 @@ listview > row {
   padding: 24px;
   border-radius: 8px;
   margin: 32px;
-  width: 80%;
 }