fastpygrid 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. fastpygrid-0.1.0/.github/workflows/publish.yml +37 -0
  2. fastpygrid-0.1.0/.gitignore +19 -0
  3. fastpygrid-0.1.0/CMakeLists.txt +22 -0
  4. fastpygrid-0.1.0/LICENSE +21 -0
  5. fastpygrid-0.1.0/PKG-INFO +146 -0
  6. fastpygrid-0.1.0/README.md +125 -0
  7. fastpygrid-0.1.0/build.bat +14 -0
  8. fastpygrid-0.1.0/demos/_data.py +113 -0
  9. fastpygrid-0.1.0/demos/demo.bat +43 -0
  10. fastpygrid-0.1.0/demos/demo_gpu_qt.py +75 -0
  11. fastpygrid-0.1.0/demos/demo_gpu_tk.py +74 -0
  12. fastpygrid-0.1.0/demos/setup.bat +32 -0
  13. fastpygrid-0.1.0/docs/screenshot.png +0 -0
  14. fastpygrid-0.1.0/pyproject.toml +38 -0
  15. fastpygrid-0.1.0/requirements.txt +3 -0
  16. fastpygrid-0.1.0/scripts/benchmarks/bench_geometry.py +72 -0
  17. fastpygrid-0.1.0/scripts/tests/check_select.py +68 -0
  18. fastpygrid-0.1.0/scripts/tests/fuzz_coremodel.py +126 -0
  19. fastpygrid-0.1.0/src/__init__.py +13 -0
  20. fastpygrid-0.1.0/src/core/__init__.py +8 -0
  21. fastpygrid-0.1.0/src/core/_gpu/surface.cpp +281 -0
  22. fastpygrid-0.1.0/src/core/_gridstore/gridcore.cpp +428 -0
  23. fastpygrid-0.1.0/src/core/coremodel.py +359 -0
  24. fastpygrid-0.1.0/src/core/filter.py +91 -0
  25. fastpygrid-0.1.0/src/core/find.py +100 -0
  26. fastpygrid-0.1.0/src/core/geometry.py +341 -0
  27. fastpygrid-0.1.0/src/core/gpu.py +1531 -0
  28. fastpygrid-0.1.0/src/core/gridcontroller.py +367 -0
  29. fastpygrid-0.1.0/src/core/model.py +991 -0
  30. fastpygrid-0.1.0/src/core/paint.py +268 -0
  31. fastpygrid-0.1.0/src/core/render.py +77 -0
  32. fastpygrid-0.1.0/src/core/selection.py +180 -0
  33. fastpygrid-0.1.0/src/core/theme.py +43 -0
  34. fastpygrid-0.1.0/src/render/__init__.py +6 -0
  35. fastpygrid-0.1.0/src/render/gpu_qt.py +247 -0
  36. fastpygrid-0.1.0/src/render/gpu_tk.py +172 -0
@@ -0,0 +1,37 @@
1
+ name: publish
2
+
3
+ # Build the Windows wheel + sdist, then publish to PyPI when a GitHub Release is
4
+ # published (or run manually). Uses PyPI Trusted Publishing (OIDC) -- no token.
5
+ on:
6
+ release:
7
+ types: [published]
8
+ workflow_dispatch: # adds a manual "Run workflow" button in the Actions tab
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: windows-latest # needs MSVC + CMake to compile the DLLs (preinstalled)
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+ - name: Build wheel + sdist
19
+ run: |
20
+ python -m pip install --upgrade build
21
+ python -m build
22
+ - uses: actions/upload-artifact@v4
23
+ with:
24
+ name: dist
25
+ path: dist/*
26
+
27
+ publish:
28
+ needs: build
29
+ runs-on: ubuntu-latest # gh-action-pypi-publish only runs on Linux
30
+ permissions:
31
+ id-token: write # required for Trusted Publishing
32
+ steps:
33
+ - uses: actions/download-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist
37
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,19 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.obj
4
+ *.exp
5
+ *.lib
6
+ *.dll
7
+ .venv/
8
+ venv/
9
+
10
+ # build output (build.bat -> .py + .dll)
11
+ dist/
12
+ # cmake build tree
13
+ build/
14
+
15
+ # staged library copy (setup.bat)
16
+ demos/fastgrid/
17
+
18
+ # python packaging
19
+ *.egg-info/
@@ -0,0 +1,22 @@
1
+ cmake_minimum_required(VERSION 3.15)
2
+ project(fastgrid_native CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ if(NOT CMAKE_BUILD_TYPE)
7
+ set(CMAKE_BUILD_TYPE Release)
8
+ endif()
9
+
10
+ # The two native DLLs. Their .cpp #pragma comment(lib, ...) the Windows libs, so
11
+ # no target_link_libraries needed. No "lib" prefix -> ctypes loads them by name.
12
+ add_library(surface SHARED src/core/_gpu/surface.cpp)
13
+ add_library(gridcore SHARED src/core/_gridstore/gridcore.cpp)
14
+ set_target_properties(surface gridcore PROPERTIES PREFIX "")
15
+
16
+ # Install lays out the `fastgrid` package: each .dll beside its module, plus the
17
+ # .py sources (no .cpp). Destinations are under fastgrid/ so this works both for
18
+ # build.bat (--prefix dist -> dist/fastgrid) and the scikit-build-core wheel
19
+ # (prefix = wheel root -> fastgrid/ in site-packages).
20
+ install(TARGETS surface RUNTIME DESTINATION fastgrid/core/_gpu LIBRARY DESTINATION fastgrid/core/_gpu)
21
+ install(TARGETS gridcore RUNTIME DESTINATION fastgrid/core/_gridstore LIBRARY DESTINATION fastgrid/core/_gridstore)
22
+ install(DIRECTORY src/ DESTINATION fastgrid FILES_MATCHING PATTERN "*.py")
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flynn Crochon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastpygrid
3
+ Version: 0.1.0
4
+ Summary: Fast, GPU-painted spreadsheet grid for tens of thousands of rows (Windows, Direct2D).
5
+ Keywords: grid,spreadsheet,table,gpu,direct2d,tkinter,qt
6
+ Author: Flynn Crochon
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Win32 (MS Windows)
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: User Interfaces
15
+ Project-URL: Homepage, https://github.com/flynncrochon/Fast-Python-Grid
16
+ Project-URL: Repository, https://github.com/flynncrochon/Fast-Python-Grid
17
+ Requires-Python: >=3.8
18
+ Provides-Extra: qt
19
+ Requires-Dist: PySide6; extra == "qt"
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Fast Python Grid
23
+
24
+ A fast, GPU-painted spreadsheet grid for tens of thousands of rows. Scroll,
25
+ select, filter and find stay instant because only the visible cells are ever
26
+ built. The grid logic lives in a GUI-free core; a single toolkit-neutral
27
+ Direct2D engine draws it, hosted by a thin Tk or Qt adapter.
28
+
29
+ ```
30
+ src/ fastgrid .py sources (no DLLs live here; becomes dist/fastgrid)
31
+ core/ model, geometry, selection, paint() -> display list, gpu.py (Direct2D engine)
32
+ _gpu/ surface.cpp
33
+ _gridstore/ gridcore.cpp
34
+ render/ gpu_tk.py (tkinter host) · gpu_qt.py (PySide6 host)
35
+ CMakeLists.txt builds the DLLs + installs the .py into dist/fastgrid/
36
+ build.bat runs CMake (configure/build/install) -> dist/fastgrid/ (the runnable library)
37
+ demos/ demo_gpu_tk.py · demo_gpu_qt.py · _data.py · setup.bat (stages demos/fastgrid + demos/.venv)
38
+ scripts/
39
+ tests/ check_select.py · fuzz_coremodel.py (import dist/fastgrid)
40
+ benchmarks/ bench_geometry.py (import dist/fastgrid)
41
+ ```
42
+
43
+ `core.paint()` returns a display list (plain-data draw ops for the visible
44
+ cells); the engine just blits it. The host only owns the window and translates
45
+ events, so the Tk and Qt hosts behave identically:
46
+
47
+ ```
48
+ dl.cells = [(x, y, w, h, text, bg, fg, flags), ...] # ~visible cells, back-to-front
49
+ dl.overlays = [("line"|"vline"|"hline"|"ring"|"tri", ...)] # chrome drawn after cells
50
+ ```
51
+
52
+ `core.paint` decides every colour, position and z-order; the engine is ~"for
53
+ each cell fill a rect + draw text; for each overlay draw a line/rect/triangle".
54
+
55
+ ![fastgrid sample grid: headers, frozen columns, per-column filters](docs/screenshot.png)
56
+
57
+ ## Requirements
58
+
59
+ | Requirement | Details |
60
+ |---|---|
61
+ | **Windows only** | The renderer is Direct2D (`_gpu/surface.dll`), so it needs Windows and a Direct3D 11-capable GPU (falls back to the WARP software device if there's no GPU). There is no macOS/Linux backend. |
62
+ | **Python 3.8+** | |
63
+ | **Tk host** | Standard library only (`tkinter`, ships with Python). |
64
+ | **Qt host** | Needs `PySide6` (`pip install PySide6`), the only dependency, and only if you use the Qt host. |
65
+ | **Native DLLs** | Not committed -- `build.bat` runs CMake (which finds MSVC itself) to compile them into `dist/fastgrid/`. `surface.dll` draws the Direct2D surface and `gridcore.dll` is an optional C++ data core (the model falls back to pure Python if it's missing). Needs CMake + MSVC on the machine. Run `build.bat` once (and after any `.cpp` or `.py` change), then run the demos. |
66
+
67
+ ## Example
68
+
69
+ ```python
70
+ from fastgrid.render.gpu_tk import make_sheet # tkinter host (stdlib only)
71
+ win = make_sheet(
72
+ ["Ticker", "Company", "Sector", "Price"],
73
+ [["AAPL", "Apple Inc.", "Technology", "189.20"],
74
+ ["XOM", "Exxon Mobil", "Energy", "104.10"]],
75
+ frozen_columns=2, # pin the first 2 columns against horizontal scroll
76
+ )
77
+ win.mainloop()
78
+ ```
79
+
80
+ Double-click or type to edit. Enter/Tab commit, Ctrl+Z/Y undo/redo, Ctrl+C/V
81
+ copy/paste, Ctrl+A select-all, Ctrl+F find, ▼ on a header to filter/sort.
82
+
83
+ ```python
84
+ from fastgrid.render.gpu_qt import make_sheet # PySide6 host
85
+ win = make_sheet(headers, rows, frozen_columns=2)
86
+ win.mainloop() # aliases app.exec()
87
+ ```
88
+
89
+ ## Build
90
+
91
+ ```bash
92
+ build.bat
93
+ ```
94
+
95
+ Runs CMake to compile the DLLs and assemble the runnable library into
96
+ `dist/fastgrid/` (`.py` + `.dll` only). Needs CMake and MSVC. Re-run after any
97
+ `.cpp` or `.py` change, then `demos/setup.bat` to stage `demos/fastgrid` + `demos/.venv`.
98
+
99
+ ## Run
100
+
101
+ ```bash
102
+ python demos/demo_gpu_tk.py # tkinter host, 100k rows
103
+ python demos/demo_gpu_qt.py # Qt host, same data
104
+ python demos/demo_gpu_tk.py --rows 500000 # stress it
105
+ python scripts/tests/check_select.py # selection-state-machine check
106
+ python scripts/tests/fuzz_coremodel.py # C++ data core vs pure-Python oracle
107
+ ```
108
+
109
+ ## Performance
110
+
111
+ Only the visible cells are built, so the row count barely matters. 10k rows
112
+ and 1M rows do the same work per frame.
113
+
114
+ ```
115
+ rows core paint()
116
+ 10,000 0.13 ms
117
+ 100,000 0.13 ms
118
+ 1,000,000 0.13 ms
119
+ ```
120
+
121
+ The Direct2D surface redraws that frame on the GPU, vsync-capped (~60 fps), and
122
+ stays smooth while scrolling millions of rows.
123
+
124
+ ## Features
125
+
126
+ The engine handles all of this, so both the Tk and Qt hosts get it for free.
127
+
128
+ | Feature | Details |
129
+ |---|---|
130
+ | Frozen columns | Pin leading columns against horizontal scroll. |
131
+ | Pinned selectable header rows | Pass a list of lists as `headers` for a multi-row header; adjacent same-label cells in the upper rows merge into spanning group bands, e.g. `[["", "FY 2023", "FY 2023"], ["Ticker", "Q1", "Q2"]]`. |
132
+ | Excel-style selection | Click/drag/Ctrl/Shift, whole row/column, select-all, Ctrl+arrow block jumps. |
133
+ | In-cell editing | Double-click or type to edit; copy/paste/delete, undo/redo. |
134
+ | Filter & sort | Per-column value and text filters, A→Z/Z→A sort. |
135
+ | Find | Ctrl+F with prev/next, case, and selection scope. |
136
+
137
+ Per-cell styling and dropdowns, and thick black section dividers, are set on
138
+ the model (display only, positional):
139
+
140
+ ```python
141
+ model.set_cell_style(gr, col, fg="#c0392b", bold=True) # gr/col are GRID coords
142
+ model.set_cell_choices(gr, col, ["Buy", "Hold", "Sell"]) # editing offers a dropdown
143
+ model.set_vline(col) # thick rule on the RIGHT edge of a column
144
+ model.set_hline(gr) # thick rule on the BOTTOM edge of a grid row
145
+ model.set_readonly_col(col) # block edits/paste/delete in a column (still selectable)
146
+ ```
@@ -0,0 +1,125 @@
1
+ # Fast Python Grid
2
+
3
+ A fast, GPU-painted spreadsheet grid for tens of thousands of rows. Scroll,
4
+ select, filter and find stay instant because only the visible cells are ever
5
+ built. The grid logic lives in a GUI-free core; a single toolkit-neutral
6
+ Direct2D engine draws it, hosted by a thin Tk or Qt adapter.
7
+
8
+ ```
9
+ src/ fastgrid .py sources (no DLLs live here; becomes dist/fastgrid)
10
+ core/ model, geometry, selection, paint() -> display list, gpu.py (Direct2D engine)
11
+ _gpu/ surface.cpp
12
+ _gridstore/ gridcore.cpp
13
+ render/ gpu_tk.py (tkinter host) · gpu_qt.py (PySide6 host)
14
+ CMakeLists.txt builds the DLLs + installs the .py into dist/fastgrid/
15
+ build.bat runs CMake (configure/build/install) -> dist/fastgrid/ (the runnable library)
16
+ demos/ demo_gpu_tk.py · demo_gpu_qt.py · _data.py · setup.bat (stages demos/fastgrid + demos/.venv)
17
+ scripts/
18
+ tests/ check_select.py · fuzz_coremodel.py (import dist/fastgrid)
19
+ benchmarks/ bench_geometry.py (import dist/fastgrid)
20
+ ```
21
+
22
+ `core.paint()` returns a display list (plain-data draw ops for the visible
23
+ cells); the engine just blits it. The host only owns the window and translates
24
+ events, so the Tk and Qt hosts behave identically:
25
+
26
+ ```
27
+ dl.cells = [(x, y, w, h, text, bg, fg, flags), ...] # ~visible cells, back-to-front
28
+ dl.overlays = [("line"|"vline"|"hline"|"ring"|"tri", ...)] # chrome drawn after cells
29
+ ```
30
+
31
+ `core.paint` decides every colour, position and z-order; the engine is ~"for
32
+ each cell fill a rect + draw text; for each overlay draw a line/rect/triangle".
33
+
34
+ ![fastgrid sample grid: headers, frozen columns, per-column filters](docs/screenshot.png)
35
+
36
+ ## Requirements
37
+
38
+ | Requirement | Details |
39
+ |---|---|
40
+ | **Windows only** | The renderer is Direct2D (`_gpu/surface.dll`), so it needs Windows and a Direct3D 11-capable GPU (falls back to the WARP software device if there's no GPU). There is no macOS/Linux backend. |
41
+ | **Python 3.8+** | |
42
+ | **Tk host** | Standard library only (`tkinter`, ships with Python). |
43
+ | **Qt host** | Needs `PySide6` (`pip install PySide6`), the only dependency, and only if you use the Qt host. |
44
+ | **Native DLLs** | Not committed -- `build.bat` runs CMake (which finds MSVC itself) to compile them into `dist/fastgrid/`. `surface.dll` draws the Direct2D surface and `gridcore.dll` is an optional C++ data core (the model falls back to pure Python if it's missing). Needs CMake + MSVC on the machine. Run `build.bat` once (and after any `.cpp` or `.py` change), then run the demos. |
45
+
46
+ ## Example
47
+
48
+ ```python
49
+ from fastgrid.render.gpu_tk import make_sheet # tkinter host (stdlib only)
50
+ win = make_sheet(
51
+ ["Ticker", "Company", "Sector", "Price"],
52
+ [["AAPL", "Apple Inc.", "Technology", "189.20"],
53
+ ["XOM", "Exxon Mobil", "Energy", "104.10"]],
54
+ frozen_columns=2, # pin the first 2 columns against horizontal scroll
55
+ )
56
+ win.mainloop()
57
+ ```
58
+
59
+ Double-click or type to edit. Enter/Tab commit, Ctrl+Z/Y undo/redo, Ctrl+C/V
60
+ copy/paste, Ctrl+A select-all, Ctrl+F find, ▼ on a header to filter/sort.
61
+
62
+ ```python
63
+ from fastgrid.render.gpu_qt import make_sheet # PySide6 host
64
+ win = make_sheet(headers, rows, frozen_columns=2)
65
+ win.mainloop() # aliases app.exec()
66
+ ```
67
+
68
+ ## Build
69
+
70
+ ```bash
71
+ build.bat
72
+ ```
73
+
74
+ Runs CMake to compile the DLLs and assemble the runnable library into
75
+ `dist/fastgrid/` (`.py` + `.dll` only). Needs CMake and MSVC. Re-run after any
76
+ `.cpp` or `.py` change, then `demos/setup.bat` to stage `demos/fastgrid` + `demos/.venv`.
77
+
78
+ ## Run
79
+
80
+ ```bash
81
+ python demos/demo_gpu_tk.py # tkinter host, 100k rows
82
+ python demos/demo_gpu_qt.py # Qt host, same data
83
+ python demos/demo_gpu_tk.py --rows 500000 # stress it
84
+ python scripts/tests/check_select.py # selection-state-machine check
85
+ python scripts/tests/fuzz_coremodel.py # C++ data core vs pure-Python oracle
86
+ ```
87
+
88
+ ## Performance
89
+
90
+ Only the visible cells are built, so the row count barely matters. 10k rows
91
+ and 1M rows do the same work per frame.
92
+
93
+ ```
94
+ rows core paint()
95
+ 10,000 0.13 ms
96
+ 100,000 0.13 ms
97
+ 1,000,000 0.13 ms
98
+ ```
99
+
100
+ The Direct2D surface redraws that frame on the GPU, vsync-capped (~60 fps), and
101
+ stays smooth while scrolling millions of rows.
102
+
103
+ ## Features
104
+
105
+ The engine handles all of this, so both the Tk and Qt hosts get it for free.
106
+
107
+ | Feature | Details |
108
+ |---|---|
109
+ | Frozen columns | Pin leading columns against horizontal scroll. |
110
+ | Pinned selectable header rows | Pass a list of lists as `headers` for a multi-row header; adjacent same-label cells in the upper rows merge into spanning group bands, e.g. `[["", "FY 2023", "FY 2023"], ["Ticker", "Q1", "Q2"]]`. |
111
+ | Excel-style selection | Click/drag/Ctrl/Shift, whole row/column, select-all, Ctrl+arrow block jumps. |
112
+ | In-cell editing | Double-click or type to edit; copy/paste/delete, undo/redo. |
113
+ | Filter & sort | Per-column value and text filters, A→Z/Z→A sort. |
114
+ | Find | Ctrl+F with prev/next, case, and selection scope. |
115
+
116
+ Per-cell styling and dropdowns, and thick black section dividers, are set on
117
+ the model (display only, positional):
118
+
119
+ ```python
120
+ model.set_cell_style(gr, col, fg="#c0392b", bold=True) # gr/col are GRID coords
121
+ model.set_cell_choices(gr, col, ["Buy", "Hold", "Sell"]) # editing offers a dropdown
122
+ model.set_vline(col) # thick rule on the RIGHT edge of a column
123
+ model.set_hline(gr) # thick rule on the BOTTOM edge of a grid row
124
+ model.set_readonly_col(col) # block edits/paste/delete in a column (still selectable)
125
+ ```
@@ -0,0 +1,14 @@
1
+ @echo off
2
+ REM Build the runnable fastgrid library into dist\fastgrid : the .py sources plus
3
+ REM freshly compiled .dll (via CMake), nothing else. CMake finds MSVC itself.
4
+ REM Run after changing any .py or .cpp, then run the demos against dist\fastgrid.
5
+ setlocal
6
+ set "ROOT=%~dp0"
7
+
8
+ if exist "%ROOT%dist" rmdir /s /q "%ROOT%dist"
9
+ cmake -S "%ROOT%." -B "%ROOT%build" -DCMAKE_BUILD_TYPE=Release || exit /b 1
10
+ cmake --build "%ROOT%build" --config Release || exit /b 1
11
+ cmake --install "%ROOT%build" --prefix "%ROOT%dist" --config Release || exit /b 1
12
+
13
+ echo [build] dist -^> %ROOT%dist\fastgrid
14
+ exit /b 0
@@ -0,0 +1,113 @@
1
+ """Shared sample data for the demos + benchmarks. Pure data: each caller puts
2
+ fastgrid on sys.path itself (demos use demos/fastgrid, scripts use dist/)."""
3
+
4
+ HEADERS = ["Ticker", "Company", "Sector", "Price", "Chg%", "Volume", "Note"]
5
+ SECTORS = ["Technology", "Energy", "Finance", "Health", "Consumer", "Utilities"]
6
+ COL_W = [110, 190, 155, 90, 80, 110, 90] # Sector (idx 2) widened for its ▼ dropdown arrow
7
+
8
+ # A batch of extra columns so the demo scrolls horizontally (exercises column
9
+ # resize / autofit). 8 quarters of revenue + a few text fields.
10
+ QUARTERS = ["%s %d" % (q, y) for y in (2023, 2024) for q in ("Q1", "Q2", "Q3", "Q4")]
11
+ HEADERS += QUARTERS + ["Analyst", "Rating", "Country", "Notes"]
12
+ COL_W += [95] * len(QUARTERS) + [140, 120, 120, 260] # Rating widened for its ▼ dropdown arrow
13
+
14
+ # Two-row grouped header: a band of "FY 2023"/"FY 2024" spanning each year's
15
+ # quarters above the field names (adjacent same-label cells merge into one
16
+ # spanning cell). Pass GROUPED_HEADERS instead of HEADERS to exercise it.
17
+ GROUPS = ([""] * 7 + ["FY %d" % y for y in (2023, 2024) for _q in range(4)]
18
+ + [""] * 4)
19
+ GROUPED_HEADERS = [GROUPS, HEADERS]
20
+ RATINGS = ["Buy", "Hold", "Sell", "Strong Buy", "Underweight"]
21
+ COUNTRIES = ["USA", "Germany", "Japan", "United Kingdom", "South Korea", "Brazil"]
22
+
23
+
24
+ def gen_rows(n):
25
+ return [[
26
+ "TIK%05d" % i,
27
+ "Company %d Inc." % i,
28
+ SECTORS[i % len(SECTORS)],
29
+ "%.2f" % (10 + (i * 7 % 9000) / 10.0),
30
+ "%+.2f" % (((i * 13) % 800 - 400) / 100.0),
31
+ str((i * 3779) % 5_000_000),
32
+ "watch" if i % 17 == 0 else "",
33
+ *("%.1fM" % (((i * (q + 7)) % 9000) / 10.0) for q in range(len(QUARTERS))),
34
+ "Analyst %d" % (i % 40),
35
+ RATINGS[i % len(RATINGS)],
36
+ COUNTRIES[i % len(COUNTRIES)],
37
+ "Longer free-text note for row %d to show autofit clipping." % i if i % 5 == 0 else "",
38
+ ] for i in range(n)]
39
+
40
+
41
+ def rows_arg(argv, default=100_000):
42
+ return int(argv[argv.index("--rows") + 1]) if "--rows" in argv else default
43
+
44
+
45
+ def style_demo(model, lo=None, hi=None):
46
+ """Data-driven per-cell styling so the demos exercise fg / bold / bg:
47
+ Chg% red/green by sign, 'Strong Buy' ratings bold-green, 'watch' notes flagged
48
+ amber. Optional [lo, hi) GRID-row range so stream_styles() can apply it in
49
+ chunks; defaults to the whole dataset."""
50
+ chg, rating, note = HEADERS.index("Chg%"), HEADERS.index("Rating"), HEADERS.index("Note")
51
+ lo = model.header_rows if lo is None else lo
52
+ hi = model._real_rows() if hi is None else hi
53
+ for gr in range(lo, hi):
54
+ v = model.cell(gr, chg)
55
+ if v:
56
+ model.set_cell_style(gr, chg, fg="#c0392b" if v.startswith("-") else "#1e8449", bold=True)
57
+ if model.cell(gr, rating) == "Strong Buy":
58
+ model.set_cell_style(gr, rating, fg="#1e8449", bold=True)
59
+ if model.cell(gr, note) == "watch":
60
+ model.set_cell_style(gr, note, bg="#fff3b0")
61
+
62
+
63
+ def stream_styles(win, chunk=4000):
64
+ """Apply the per-cell style pass in chunks AFTER the first frame, so the grid
65
+ shows instantly and never freezes on load. The first chunk covers the visible
66
+ rows, so what you see is styled within a frame; the rest streams in behind it."""
67
+ m, view = win.model, win.grid_view
68
+ total = m._real_rows()
69
+
70
+ def step(lo):
71
+ style_demo(m, lo, min(lo + chunk, total))
72
+ m.changed()
73
+ if lo + chunk < total:
74
+ view.after(1, lambda: step(lo + chunk))
75
+
76
+ view.after(0, lambda: step(m.header_rows))
77
+
78
+
79
+ # Stress dropdown: 1000 options, most far wider than any column -- exercises the
80
+ # popup-widening (list grows to the text) and large option lists. The whole column
81
+ # shares one interned tuple, so this costs one list, not one copy per row.
82
+ _LONG = ("Comprehensive multi-word option label that is far wider than the column",
83
+ "Another lengthy descriptive choice overflowing the narrow cell by a lot",
84
+ "Short",
85
+ "Medium-length option text")
86
+ LONG_OPTS = ["%04d — %s" % (i, _LONG[i % len(_LONG)]) for i in range(1000)]
87
+
88
+
89
+ def lines_demo(model):
90
+ """Thick black section dividers: a vertical rule right of the Chg% column
91
+ (splits the identity/price block from the quarterly financials) and a couple
92
+ of horizontal rules to band the data. Positional -- they mark a fixed place."""
93
+ model.set_vline(HEADERS.index("Chg%"))
94
+ model.set_hline(10) # under grid row 10 (a data row)
95
+ model.set_hline(25)
96
+
97
+
98
+ def readonly_demo(model):
99
+ """Lock the Ticker + Price columns: still selectable and copyable, but edits,
100
+ paste and delete are rejected (try double-clicking a Ticker cell -- no editor)."""
101
+ model.set_readonly_col(HEADERS.index("Ticker"))
102
+ model.set_readonly_col(HEADERS.index("Price"))
103
+
104
+
105
+ def choices_demo(model):
106
+ """Make Sector/Rating dropdowns, plus a 1000-option long-text dropdown on the
107
+ narrow Note column (to exercise popup widening). Same bulk-before-build note as
108
+ style_demo."""
109
+ sector, rating, note = (HEADERS.index("Sector"), HEADERS.index("Rating"),
110
+ HEADERS.index("Note"))
111
+ model.set_col_choices(sector, SECTORS) # whole-column dropdowns: O(1), not per row
112
+ model.set_col_choices(rating, RATINGS)
113
+ model.set_col_choices(note, LONG_OPTS)
@@ -0,0 +1,43 @@
1
+ @echo off
2
+ REM Launch the fastgrid Tk or Qt GPU demo using the project's .venv (Python 3.10).
3
+ REM Usage: demo.bat -> prompts for tk or qt
4
+ REM demo.bat qt -> runs the Qt (PySide6) demo
5
+ REM demo.bat tk -> runs the Tk demo
6
+ REM demo.bat qt --rows 500000 -> extra args pass through to the demo
7
+
8
+ setlocal
9
+ REM This bat lives in demos/, so ROOT is its parent (the repo root).
10
+ for %%I in ("%~dp0..") do set "ROOT=%%~fI\"
11
+ set "PY=%~dp0.venv\Scripts\python.exe"
12
+
13
+ if not exist "%PY%" (
14
+ echo [demo] demos\.venv not found. Run setup.bat first.
15
+ exit /b 1
16
+ )
17
+
18
+ set "TARGET=%~1"
19
+ if /i "%TARGET%"=="qt" goto run
20
+ if /i "%TARGET%"=="tk" goto run
21
+
22
+ set /p TARGET=Which demo? [tk/qt]:
23
+ if /i not "%TARGET%"=="qt" if /i not "%TARGET%"=="tk" (
24
+ echo [demo] Unknown demo "%TARGET%". Choose tk or qt.
25
+ exit /b 1
26
+ )
27
+ set "REST="
28
+ goto launch
29
+
30
+ :run
31
+ REM Drop the first arg (tk/qt); keep the rest to pass through.
32
+ shift
33
+ set "REST="
34
+ :collect
35
+ if "%~1"=="" goto launch
36
+ set "REST=%REST% %1"
37
+ shift
38
+ goto collect
39
+
40
+ :launch
41
+ echo [demo] Launching %TARGET% demo...
42
+ "%PY%" "%ROOT%demos\demo_gpu_%TARGET%.py"%REST%
43
+ exit /b %errorlevel%
@@ -0,0 +1,75 @@
1
+ """Direct2D/GPU demo under a Qt (PySide6) host -- same engine as demo_gpu_tk.py.
2
+
3
+ python demos/demo_gpu_qt.py # 100k rows on the GPU surface
4
+ python demos/demo_gpu_qt.py --rows 500000 # stress it
5
+
6
+ Tabs across the top open separate whole sheets, each with different options, so you
7
+ can compare them live. The "Uncapped" tabs scroll past the last row/column into
8
+ empty space (Excel-style): the scrollbar thumb shrinks as you overscroll and snaps
9
+ back when you scroll in again -- unless you typed out there, which grows the sheet.
10
+
11
+ Proves the toolkit-neutral GpuEngine runs unchanged under Qt. Build the DLL once:
12
+ python -m fastgrid.core.gpu --build
13
+ """
14
+ import sys
15
+
16
+ # _data.py and fastgrid/ both live next to this file (fastgrid staged by setup.bat).
17
+ from _data import (HEADERS, COL_W, gen_rows, rows_arg, stream_styles, choices_demo,
18
+ lines_demo, readonly_demo)
19
+
20
+ # Each tab = a whole separate sheet with its own scroll-cap options.
21
+ SHEETS = [
22
+ ("Capped (default)", {}),
23
+ ("Uncapped rows", dict(uncap_rows=True)),
24
+ ("Uncapped rows + cols", dict(uncap_rows=True, uncap_cols=True)),
25
+ ]
26
+
27
+
28
+ def _add_sheet(tabs, title, headers, rows, col_w, scale, lib, **opts):
29
+ """Build one grid (its own model + engine) into a fresh Qt tab page."""
30
+ from PySide6 import QtWidgets
31
+ from fastgrid.render.gpu_qt import GpuQtGrid
32
+ from fastgrid.core.coremodel import make_model
33
+ page = QtWidgets.QWidget()
34
+ lay = QtWidgets.QVBoxLayout(page)
35
+ lay.setContentsMargins(0, 0, 0, 0)
36
+ model = make_model(headers, rows, editable=True)
37
+ grid = GpuQtGrid(page, model, frozen=2, col_w=col_w, scale=scale, lib=lib, **opts)
38
+ lay.addWidget(grid)
39
+ page.model, page.grid_view = model, grid # stream_styles() reads these
40
+ tabs.addTab(page, title)
41
+ choices_demo(model) # Sector/Rating dropdowns (O(1), whole-column)
42
+ lines_demo(model) # thick section dividers
43
+ readonly_demo(model) # locked Ticker + Price columns
44
+ model.changed() # first frame paints instantly with data + dropdowns
45
+ stream_styles(page) # per-cell fg/bold/bg streams in after the first frame
46
+
47
+
48
+ def main():
49
+ from PySide6 import QtWidgets
50
+ from fastgrid.core.gpu import _load_lib, _enable_dpi_awareness, _screen_scale
51
+ lib = _load_lib()
52
+ if lib is None:
53
+ raise SystemExit("Gpu surface unavailable -- build it with "
54
+ "`python -m fastgrid.core.gpu --build`.")
55
+ app = QtWidgets.QApplication.instance()
56
+ if app is None:
57
+ _enable_dpi_awareness()
58
+ app = QtWidgets.QApplication([])
59
+ n = rows_arg(sys.argv)
60
+ data = gen_rows(n) # generate once; each sheet's model copies it
61
+ scale = _screen_scale(None)
62
+ # uncapped tabs use a SMALL sheet so the empty repeatable cells are right past
63
+ # the data (with 100k rows you'd never scroll to the phantom region to see them).
64
+ small = data if n <= 30 else gen_rows(30)
65
+ win = QtWidgets.QTabWidget()
66
+ win.setWindowTitle("fastgrid (gpu-qt) — sheet options — %s rows" % f"{n:,}")
67
+ win.resize(round(980 * scale), round(620 * scale))
68
+ for title, opts in SHEETS:
69
+ _add_sheet(win, title, HEADERS, (small if opts else data), COL_W, scale, lib, **opts)
70
+ win.show()
71
+ app.exec()
72
+
73
+
74
+ if __name__ == "__main__":
75
+ main()