Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.2.0 - 2026-04-05

- Refined the desktop UI with a darker theme, tighter toolbar, custom item icons, and cleaner loading feedback
- Added icon and detail browsing improvements including type-to-filter, breadcrumbs, and better context menu organization
- Enhanced the Properties panel with responsive image thumbnails, scrolling content, and on-demand sequence actions
- Improved path handling for symlinked directories and polished overall navigation, search, and panel behavior

## 0.1.2 - 2026-04-05

- Moved directory scanning into a subprocess worker for safer cancellation
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "sview"
version = "0.1.2"
version = "0.2.0"
description = "Sequence-aware filesystem browser"
readme = "README.md"
requires-python = ">=3.8"
Expand Down
Binary file modified sview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion sview/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@
on usability and a clean interface.
"""

__version__ = "0.1.2"
__version__ = "0.2.0"
132 changes: 108 additions & 24 deletions sview/qt/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ def _wants_debug(args: list[str]) -> bool:

def _apply_dark_theme(app: QApplication) -> None:
palette = QPalette()
palette.setColor(QPalette.ColorRole.Window, QColor("#14191e"))
palette.setColor(QPalette.ColorRole.Window, QColor("#101519"))
palette.setColor(QPalette.ColorRole.WindowText, QColor("#dbe2e8"))
palette.setColor(QPalette.ColorRole.Base, QColor("#11161b"))
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#171d22"))
palette.setColor(QPalette.ColorRole.Base, QColor("#0d1216"))
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#141b20"))
palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#11161b"))
palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#f2f5f8"))
palette.setColor(QPalette.ColorRole.Text, QColor("#dbe2e8"))
palette.setColor(QPalette.ColorRole.Button, QColor("#1f262d"))
palette.setColor(QPalette.ColorRole.Button, QColor("#161d23"))
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#edf2f7"))
palette.setColor(QPalette.ColorRole.Highlight, QColor("#313b44"))
palette.setColor(QPalette.ColorRole.Highlight, QColor("#2a333b"))
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#75818d"))
app.setPalette(palette)
Expand All @@ -106,35 +106,65 @@ def _apply_dark_theme(app: QApplication) -> None:
font-size: 12px;
}
QTreeView, QTableWidget, QLineEdit, QPushButton, QLabel, QListWidget {
background-color: #1e252c;
background-color: #1b232a;
color: #dbe2e8;
}
QMenuBar {
background-color: #0f1519;
color: #d5dde4;
padding: 1px 4px;
}
QMenuBar::item {
background: transparent;
padding: 4px 8px;
}
QMenuBar::item:selected {
background-color: #1e272f;
}
QMenu {
background-color: #151c22;
color: #dbe2e8;
border: 1px solid #26303a;
}
QMenu::item {
padding: 6px 18px;
}
QMenu::item:selected {
background-color: #27313a;
}
QMainWindow, QWidget#mainContent {
background-color: #13191f;
background-color: #0f1418;
color: #dbe2e8;
}
QHeaderView::section {
background-color: #1e252c;
background-color: #171f26;
color: #d4dde4;
padding: 4px 6px;
border: 0;
border-right: 1px solid #29333c;
border-right: 1px solid #222b33;
}
QTreeView, QTableWidget, QLineEdit, QListWidget {
background-color: #161d23;
border: 1px solid #222c34;
background-color: #141b20;
border: 1px solid #1f2830;
border-radius: 3px;
}
QTreeView {
background-color: #171f26;
}
QLineEdit#searchInput {
background-color: #171e24;
border: 1px solid #202933;
border-radius: 6px;
padding: 0 12px;
font-size: 13px;
}
QScrollBar:vertical {
background: #10161a;
background: #0e1317;
width: 10px;
margin: 2px;
}
QScrollBar::handle:vertical {
background: #36424c;
background: #34414a;
min-height: 28px;
border-radius: 4px;
}
Expand All @@ -159,23 +189,33 @@ def _apply_dark_theme(app: QApplication) -> None:
border: none;
}
QTableWidget::item:selected, QTreeView::item:selected {
background-color: #2c353d;
background-color: #273039;
color: white;
}
QPushButton {
background-color: #20282f;
border: 1px solid #29333c;
background-color: #1a2127;
border: 1px solid #25303a;
border-radius: 3px;
padding: 4px 9px;
min-height: 24px;
}
QPushButton#navButton, QPushButton#toolbarToggle {
background-color: transparent;
border: 0;
border-radius: 5px;
padding: 0;
min-height: 28px;
}
QPushButton#navButton:hover, QPushButton#toolbarToggle:hover {
background-color: #1e262d;
}
QPushButton:checked {
background-color: #2b3640;
background-color: #263039;
border-color: #34414d;
}
QPushButton:disabled {
color: #697581;
background-color: #1a2127;
background-color: #151b20;
}
QToolButton {
background: transparent;
Expand All @@ -186,11 +226,48 @@ def _apply_dark_theme(app: QApplication) -> None:
color: #d6dde4;
}
QStatusBar {
background-color: #141b20;
background-color: #10161b;
color: #b6c1ca;
}
QWidget#breadcrumbBar {
background-color: #11181d;
border: 1px solid #1e2831;
border-radius: 5px;
padding: 1px 4px;
}
QToolButton#breadcrumbButton {
background: transparent;
border: 0;
color: #cfd8e0;
padding: 2px 3px;
border-radius: 4px;
}
QToolButton#breadcrumbButton:hover {
background-color: #1f2931;
color: #f2f6f9;
}
QLabel#breadcrumbSeparator {
color: #6f7e8b;
padding: 0;
}
QLabel#loadingOverlay {
color: #8b99a5;
padding: 2px 0;
}
QProgressBar#loadingSpinner {
background: transparent;
border: 0;
min-width: 72px;
max-width: 72px;
min-height: 6px;
max-height: 6px;
}
QProgressBar#loadingSpinner::chunk {
background-color: #5f7f95;
border-radius: 3px;
}
QSplitter::handle {
background-color: #36414a;
background-color: #2c3740;
width: 1px;
}
QLabel#inspectorTitle {
Expand All @@ -201,14 +278,21 @@ def _apply_dark_theme(app: QApplication) -> None:
color: #8998a5;
margin-bottom: 6px;
}
QLabel#inspectorThumbnail {
background-color: #12181d;
border: 1px solid #202932;
border-radius: 6px;
color: #7f8d99;
padding: 6px;
}
QWidget#iconCard {
background-color: #1c232a;
border: 1px solid #252f37;
background-color: #182026;
border: 1px solid #202a32;
border-radius: 6px;
}
QWidget#iconCard:hover {
background-color: #212a32;
border-color: #2f3b45;
background-color: #1d262e;
border-color: #2b3741;
}
QLabel#iconCardTitle {
font-size: 13px;
Expand Down
53 changes: 41 additions & 12 deletions sview/qt/icon_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,19 @@
QLabel,
QListWidget,
QListWidgetItem,
QStyle,
QVBoxLayout,
QWidget,
)

from sview.model import BrowserItem, ItemType
from sview.qt.icons import browser_item_icon


class ContentsIconView(QListWidget):
context_requested = Signal(object, object)
filter_text_typed = Signal(str)
filter_backspace_requested = Signal()
filter_clear_requested = Signal()
CARD_WIDTH = 270
CARD_HEIGHT = 88

Expand All @@ -63,8 +66,9 @@ def __init__(self) -> None:
self.setMovement(QListWidget.Movement.Static)
self.setWrapping(True)
self.setSelectionMode(QListWidget.SelectionMode.SingleSelection)
self.setSpacing(14)
self.setIconSize(QSize(44, 44))
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setSpacing(12)
self.setIconSize(QSize(52, 52))
self.setGridSize(QSize(self.CARD_WIDTH, self.CARD_HEIGHT))
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self._emit_context_request)
Expand Down Expand Up @@ -103,25 +107,24 @@ def _emit_context_request(self, position) -> None:
self.setCurrentItem(item)
self.context_requested.emit(browser_item, self.viewport().mapToGlobal(position))

def keyPressEvent(self, event) -> None:
if self._handle_filter_key(event):
return
super().keyPressEvent(event)

def _icon(self, item: BrowserItem):
if item.item_type is ItemType.DIRECTORY:
return self.style().standardIcon(QStyle.StandardPixmap.SP_DirIcon)
if item.item_type is ItemType.SEQUENCE:
return self.style().standardIcon(
QStyle.StandardPixmap.SP_FileDialogDetailedView
)
return self.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon)
return browser_item_icon(item, size=52)

def _build_card(self, item: BrowserItem) -> QWidget:
card = QWidget()
card.setObjectName("iconCard")
layout = QHBoxLayout(card)
layout.setContentsMargins(14, 12, 14, 12)
layout.setSpacing(12)
layout.setSpacing(14)

icon_label = QLabel()
icon_label.setPixmap(self._icon(item).pixmap(self.iconSize()))
icon_label.setFixedSize(48, 48)
icon_label.setFixedSize(56, 56)
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

name_label = QLabel(item.display_name)
Expand Down Expand Up @@ -182,3 +185,29 @@ def _format_mtime(timestamp: float) -> str:
if timestamp <= 0:
return ""
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M")

def _handle_filter_key(self, event) -> bool:
if event.modifiers() & (
Qt.KeyboardModifier.ControlModifier
| Qt.KeyboardModifier.AltModifier
| Qt.KeyboardModifier.MetaModifier
):
return False
if event.key() == Qt.Key.Key_Backspace:
self.filter_backspace_requested.emit()
event.accept()
return True
if event.key() == Qt.Key.Key_Delete:
self.filter_clear_requested.emit()
event.accept()
return True
if event.key() == Qt.Key.Key_Escape:
self.filter_clear_requested.emit()
event.accept()
return True
text = event.text()
if text and text.isprintable():
self.filter_text_typed.emit(text)
event.accept()
return True
return False
Loading
Loading