desklab 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.
- desklab-0.1.0/PKG-INFO +121 -0
- desklab-0.1.0/README.md +98 -0
- desklab-0.1.0/pyproject.toml +38 -0
- desklab-0.1.0/setup.cfg +4 -0
- desklab-0.1.0/src/desklab/__init__.py +9 -0
- desklab-0.1.0/src/desklab/_assets/__init__.py +0 -0
- desklab-0.1.0/src/desklab/_assets/desklab.png +0 -0
- desklab-0.1.0/src/desklab/_check/__init__.py +10 -0
- desklab-0.1.0/src/desklab/_check/_check_operations/__init__.py +9 -0
- desklab-0.1.0/src/desklab/_check/_check_operations/_check.py +14 -0
- desklab-0.1.0/src/desklab/_check/_check_operations/_length.py +33 -0
- desklab-0.1.0/src/desklab/_check/_check_operations/_range.py +41 -0
- desklab-0.1.0/src/desklab/_check/_value.py +31 -0
- desklab-0.1.0/src/desklab/_utils/__init__.py +10 -0
- desklab-0.1.0/src/desklab/_utils/_geometry.py +36 -0
- desklab-0.1.0/src/desklab/_utils/_imageio_ffmpeg_exe.py +163 -0
- desklab-0.1.0/src/desklab/_utils/_regex.py +5 -0
- desklab-0.1.0/src/desklab/areas/__init__.py +9 -0
- desklab-0.1.0/src/desklab/areas/_area_interface.py +17 -0
- desklab-0.1.0/src/desklab/areas/_clickable_area.py +27 -0
- desklab-0.1.0/src/desklab/areas/_rectangular_area.py +102 -0
- desklab-0.1.0/src/desklab/components/__init__.py +15 -0
- desklab-0.1.0/src/desklab/components/_button.py +61 -0
- desklab-0.1.0/src/desklab/components/_drag_drop.py +58 -0
- desklab-0.1.0/src/desklab/components/_drawing_area.py +205 -0
- desklab-0.1.0/src/desklab/components/_text.py +117 -0
- desklab-0.1.0/src/desklab/components/_text_input.py +344 -0
- desklab-0.1.0/src/desklab/components/_window.py +124 -0
- desklab-0.1.0/src/desklab/containers/__init__.py +9 -0
- desklab-0.1.0/src/desklab/containers/_constants.py +18 -0
- desklab-0.1.0/src/desklab/containers/_flexbox.py +61 -0
- desklab-0.1.0/src/desklab/containers/_flexbox_interface.py +205 -0
- desklab-0.1.0/src/desklab/containers/_protected_flexbox.py +133 -0
- desklab-0.1.0/src/desklab/entity_types/__init__.py +20 -0
- desklab-0.1.0/src/desklab/entity_types/_colorable.py +26 -0
- desklab-0.1.0/src/desklab/entity_types/_containable.py +12 -0
- desklab-0.1.0/src/desklab/entity_types/_copiable.py +19 -0
- desklab-0.1.0/src/desklab/entity_types/_dimensionable.py +25 -0
- desklab-0.1.0/src/desklab/entity_types/_displayable.py +14 -0
- desklab-0.1.0/src/desklab/entity_types/_entity.py +5 -0
- desklab-0.1.0/src/desklab/entity_types/_event_sensitive.py +27 -0
- desklab-0.1.0/src/desklab/entity_types/_positionable.py +22 -0
- desklab-0.1.0/src/desklab/exceptions/__init__.py +12 -0
- desklab-0.1.0/src/desklab/exceptions/_exceptions.py +37 -0
- desklab-0.1.0/src/desklab/listeners/__init__.py +38 -0
- desklab-0.1.0/src/desklab/listeners/_clipboard.py +18 -0
- desklab-0.1.0/src/desklab/listeners/_default.py +21 -0
- desklab-0.1.0/src/desklab/listeners/_hover.py +36 -0
- desklab-0.1.0/src/desklab/listeners/_keyboard.py +71 -0
- desklab-0.1.0/src/desklab/listeners/_mouse.py +26 -0
- desklab-0.1.0/src/desklab/listeners/_protected_listener.py +89 -0
- desklab-0.1.0/src/desklab/listeners/_system_listener.py +46 -0
- desklab-0.1.0/src/desklab/media/__init__.py +7 -0
- desklab-0.1.0/src/desklab/media/_audio.py +55 -0
- desklab-0.1.0/src/desklab/media/_image.py +64 -0
- desklab-0.1.0/src/desklab/primitives/__init__.py +7 -0
- desklab-0.1.0/src/desklab/primitives/_color.py +142 -0
- desklab-0.1.0/src/desklab/primitives/_font.py +86 -0
- desklab-0.1.0/src/desklab/system/__init__.py +12 -0
- desklab-0.1.0/src/desklab/system/_clipboard.py +248 -0
- desklab-0.1.0/src/desklab/system/_keyboard.py +148 -0
- desklab-0.1.0/src/desklab/system/_mouse.py +63 -0
- desklab-0.1.0/src/desklab/system/_system_input.py +23 -0
- desklab-0.1.0/src/desklab.egg-info/PKG-INFO +121 -0
- desklab-0.1.0/src/desklab.egg-info/SOURCES.txt +66 -0
- desklab-0.1.0/src/desklab.egg-info/dependency_links.txt +1 -0
- desklab-0.1.0/src/desklab.egg-info/requires.txt +11 -0
- desklab-0.1.0/src/desklab.egg-info/top_level.txt +1 -0
desklab-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: desklab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simple and easy to use interface package
|
|
5
|
+
Author: Jonatas Cortes
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: pygame==2.6.1
|
|
15
|
+
Requires-Dist: pynput==1.8.1
|
|
16
|
+
Requires-Dist: pyobjc-core==12.1; sys_platform == "darwin"
|
|
17
|
+
Requires-Dist: pyobjc-framework-ApplicationServices==12.1; sys_platform == "darwin"
|
|
18
|
+
Requires-Dist: pyobjc-framework-Cocoa==12.1; sys_platform == "darwin"
|
|
19
|
+
Requires-Dist: pyobjc-framework-CoreText==12.1; sys_platform == "darwin"
|
|
20
|
+
Requires-Dist: pyobjc-framework-Quartz==12.1; sys_platform == "darwin"
|
|
21
|
+
Requires-Dist: pyperclip==1.11.0
|
|
22
|
+
Requires-Dist: six==1.17.0
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
DeskLab is a Python interface library designed for small projects that prioritize fast development over extensive customization.
|
|
27
|
+
|
|
28
|
+
It simulates the web development programing style, with separation of responsibilities and reusable components.
|
|
29
|
+
|
|
30
|
+
## 🛠️ Installation & Development Setup
|
|
31
|
+
|
|
32
|
+
If you want to clone this repository to contribute to the code, run tests, or develop features locally, follow the steps below using **`uv`**, a fast Python package installer and environment manager.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Windows
|
|
36
|
+
|
|
37
|
+
Open **PowerShell** and run:
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Alternative:
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
winget install astral-sh.uv
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Linux / macOS
|
|
52
|
+
|
|
53
|
+
Open a terminal and run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Alternative on macOS using Homebrew:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
brew install uv
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Alternative on Linux using pip:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install uv
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
After installation, restart your terminal so the `uv` command becomes available.
|
|
72
|
+
|
|
73
|
+
Verify installation:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uv --version
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Clone the Repository
|
|
82
|
+
|
|
83
|
+
Clone the project and enter its directory:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/your-username/desklab.git
|
|
87
|
+
cd desklab
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### Install Dependencies & Create Environment
|
|
93
|
+
|
|
94
|
+
You do **not** need to manually create a virtual environment or run `pip install`.
|
|
95
|
+
|
|
96
|
+
Simply execute:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
uv sync
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This command automatically:
|
|
103
|
+
|
|
104
|
+
- Creates a local virtual environment (`.venv/`)
|
|
105
|
+
- Installs all project dependencies
|
|
106
|
+
- Installs desklab in editable mode
|
|
107
|
+
- Synchronizes dependencies from `pyproject.toml`
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### Running the Project
|
|
112
|
+
|
|
113
|
+
Run your application inside the managed environment:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv run <your_file>.py
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This guarantees execution inside the project's environment and avoids dependency conflicts.
|
|
120
|
+
|
|
121
|
+
---
|
desklab-0.1.0/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
DeskLab is a Python interface library designed for small projects that prioritize fast development over extensive customization.
|
|
4
|
+
|
|
5
|
+
It simulates the web development programing style, with separation of responsibilities and reusable components.
|
|
6
|
+
|
|
7
|
+
## 🛠️ Installation & Development Setup
|
|
8
|
+
|
|
9
|
+
If you want to clone this repository to contribute to the code, run tests, or develop features locally, follow the steps below using **`uv`**, a fast Python package installer and environment manager.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Windows
|
|
13
|
+
|
|
14
|
+
Open **PowerShell** and run:
|
|
15
|
+
|
|
16
|
+
```powershell
|
|
17
|
+
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Alternative:
|
|
21
|
+
|
|
22
|
+
```powershell
|
|
23
|
+
winget install astral-sh.uv
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
### Linux / macOS
|
|
29
|
+
|
|
30
|
+
Open a terminal and run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Alternative on macOS using Homebrew:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
brew install uv
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Alternative on Linux using pip:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install uv
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After installation, restart your terminal so the `uv` command becomes available.
|
|
49
|
+
|
|
50
|
+
Verify installation:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv --version
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### Clone the Repository
|
|
59
|
+
|
|
60
|
+
Clone the project and enter its directory:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/your-username/desklab.git
|
|
64
|
+
cd desklab
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### Install Dependencies & Create Environment
|
|
70
|
+
|
|
71
|
+
You do **not** need to manually create a virtual environment or run `pip install`.
|
|
72
|
+
|
|
73
|
+
Simply execute:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uv sync
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This command automatically:
|
|
80
|
+
|
|
81
|
+
- Creates a local virtual environment (`.venv/`)
|
|
82
|
+
- Installs all project dependencies
|
|
83
|
+
- Installs desklab in editable mode
|
|
84
|
+
- Synchronizes dependencies from `pyproject.toml`
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### Running the Project
|
|
89
|
+
|
|
90
|
+
Run your application inside the managed environment:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uv run <your_file>.py
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This guarantees execution inside the project's environment and avoids dependency conflicts.
|
|
97
|
+
|
|
98
|
+
---
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "desklab"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Simple and easy to use interface package"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Jonatas Cortes"}
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pygame==2.6.1",
|
|
24
|
+
"pynput==1.8.1",
|
|
25
|
+
"pyobjc-core==12.1; sys_platform == 'darwin'",
|
|
26
|
+
"pyobjc-framework-ApplicationServices==12.1; sys_platform == 'darwin'",
|
|
27
|
+
"pyobjc-framework-Cocoa==12.1; sys_platform == 'darwin'",
|
|
28
|
+
"pyobjc-framework-CoreText==12.1; sys_platform == 'darwin'",
|
|
29
|
+
"pyobjc-framework-Quartz==12.1; sys_platform == 'darwin'",
|
|
30
|
+
"pyperclip==1.11.0",
|
|
31
|
+
"six==1.17.0"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
"desklab._assets" = ["*"]
|
desklab-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Any, Callable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Check:
|
|
6
|
+
def __init__(self, rule: Callable[[Any], bool], description: str | None = None) -> None:
|
|
7
|
+
self.__rule = rule
|
|
8
|
+
self.__description = description
|
|
9
|
+
|
|
10
|
+
def __call__(self, *args: Any, **kwargs: Any) -> bool:
|
|
11
|
+
return self.__rule(*args, **kwargs)
|
|
12
|
+
|
|
13
|
+
def __str__(self) -> str:
|
|
14
|
+
return self.__description or self.__rule.__name__
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from desklab._check._check_operations import Check
|
|
4
|
+
from desklab.exceptions import InvalidParameterValue
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CheckLength(Check):
|
|
8
|
+
|
|
9
|
+
def __init__(self, reference_length: int, comparison: str = "=", variable_name: str | None = None) -> None:
|
|
10
|
+
|
|
11
|
+
operators = {"=", ">", "<", ">=", "<="}
|
|
12
|
+
if comparison not in operators:
|
|
13
|
+
raise InvalidParameterValue(f"comparison", comparison,
|
|
14
|
+
f"'comparison' must be one of: {operators}")
|
|
15
|
+
|
|
16
|
+
def compare(value: Any) -> bool:
|
|
17
|
+
if comparison == "=":
|
|
18
|
+
return len(value) == reference_length
|
|
19
|
+
elif comparison == ">":
|
|
20
|
+
return len(value) > reference_length
|
|
21
|
+
elif comparison == "<":
|
|
22
|
+
return len(value) < reference_length
|
|
23
|
+
elif comparison == ">=":
|
|
24
|
+
return len(value) >= reference_length
|
|
25
|
+
elif comparison == "<=":
|
|
26
|
+
return len(value) <= reference_length
|
|
27
|
+
raise InvalidParameterValue(f"comparison", comparison,
|
|
28
|
+
f"'comparison' must be one of: {operators}")
|
|
29
|
+
|
|
30
|
+
name = variable_name or "Value"
|
|
31
|
+
message = f"length({name}) {comparison} {reference_length}"
|
|
32
|
+
|
|
33
|
+
super().__init__(compare, message)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Any, cast
|
|
3
|
+
from desklab.exceptions import MissingParameters
|
|
4
|
+
from desklab._check._check_operations import Check
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CheckRange(Check):
|
|
9
|
+
|
|
10
|
+
def __init__(self, min_value: Any = None, max_value: Any = None, variable_name: str | None = None) -> None:
|
|
11
|
+
|
|
12
|
+
if min_value is None and max_value is None:
|
|
13
|
+
raise MissingParameters(["min_value", "max_value"],
|
|
14
|
+
"At least one of 'min_value' or 'max_value' must be provided")
|
|
15
|
+
|
|
16
|
+
def is_valid(value: Any) -> bool:
|
|
17
|
+
|
|
18
|
+
if isinstance(value, Iterable) and not isinstance(value, (str, bytes)):
|
|
19
|
+
iterable = cast(Iterable[Any], value)
|
|
20
|
+
return all(is_valid(v) for v in iterable)
|
|
21
|
+
|
|
22
|
+
if min_value is not None and value < min_value:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
if max_value is not None and value > max_value:
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
name = variable_name or "Value"
|
|
31
|
+
|
|
32
|
+
if min_value is not None and max_value is not None:
|
|
33
|
+
message = f"{name} must be between {min_value} and {max_value}"
|
|
34
|
+
|
|
35
|
+
elif min_value is not None:
|
|
36
|
+
message = f"{name} must be greater than or equal to {min_value}"
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
message = f"{name} must be less than or equal to {max_value}"
|
|
40
|
+
|
|
41
|
+
super().__init__(is_valid, message)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Callable, TypeVar, ParamSpec
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from desklab.exceptions import InvalidParameterValue
|
|
5
|
+
from desklab._check._check_operations import Check
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
P = ParamSpec("P")
|
|
9
|
+
R = TypeVar("R")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def value_check(**validations: Check) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
13
|
+
def decorator(function: Callable[P, R]) -> Callable[P, R]:
|
|
14
|
+
signature = inspect.signature(function)
|
|
15
|
+
|
|
16
|
+
@wraps(function)
|
|
17
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
18
|
+
bound_arguments = signature.bind(*args, **kwargs)
|
|
19
|
+
bound_arguments.apply_defaults()
|
|
20
|
+
|
|
21
|
+
for param_name, param_value in bound_arguments.arguments.items():
|
|
22
|
+
if param_name in ("self", "cls"):
|
|
23
|
+
continue
|
|
24
|
+
validation_rule = validations.get(param_name)
|
|
25
|
+
if validation_rule is not None and not validation_rule(param_value):
|
|
26
|
+
raise InvalidParameterValue(param_name, param_value,
|
|
27
|
+
str(validation_rule))
|
|
28
|
+
|
|
29
|
+
return function(*args, **kwargs)
|
|
30
|
+
return wrapper
|
|
31
|
+
return decorator
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from ._geometry import point_to_segment_distance, is_inside_circle
|
|
2
|
+
from ._regex import camel_case_to_snake_case
|
|
3
|
+
from ._imageio_ffmpeg_exe import get_ffmpeg_exe
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"point_to_segment_distance",
|
|
7
|
+
"is_inside_circle",
|
|
8
|
+
"camel_case_to_snake_case",
|
|
9
|
+
"get_ffmpeg_exe"
|
|
10
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def point_to_segment_distance(point: tuple[int, int], segment_start: tuple[int, int], segment_end: tuple[int, int]) -> float:
|
|
5
|
+
point_x, point_y = point
|
|
6
|
+
start_x, start_y = segment_start
|
|
7
|
+
end_x, end_y = segment_end
|
|
8
|
+
|
|
9
|
+
vector_segment_x = end_x - start_x
|
|
10
|
+
vector_segment_y = end_y - start_y
|
|
11
|
+
|
|
12
|
+
vector_point_x = point_x - start_x
|
|
13
|
+
vector_point_y = point_y - start_y
|
|
14
|
+
|
|
15
|
+
segment_length_squared = vector_segment_x ** 2 + vector_segment_y ** 2
|
|
16
|
+
|
|
17
|
+
if segment_length_squared == 0:
|
|
18
|
+
return math.sqrt((point_x - start_x) ** 2 + (point_y - start_y) ** 2)
|
|
19
|
+
|
|
20
|
+
dot_product = vector_point_x * vector_segment_x + \
|
|
21
|
+
vector_point_y * vector_segment_y
|
|
22
|
+
projection_ratio = max(0.0, min(1.0, dot_product / segment_length_squared))
|
|
23
|
+
|
|
24
|
+
closest_x = start_x + vector_segment_x * projection_ratio
|
|
25
|
+
closest_y = start_y + vector_segment_y * projection_ratio
|
|
26
|
+
|
|
27
|
+
distance_x = point_x - closest_x
|
|
28
|
+
distance_y = point_y - closest_y
|
|
29
|
+
|
|
30
|
+
return math.sqrt(distance_x ** 2 + distance_y ** 2)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_inside_circle(coordinates: tuple[int, int], circle_center: tuple[int, int], circle_radius: int):
|
|
34
|
+
x, y = coordinates
|
|
35
|
+
cx, cy = circle_center
|
|
36
|
+
return (x - cx)**2 + (y - cy)**2 <= circle_radius**2
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# The following code block or function incorporates code from 'imageio'.
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2019-2025, imageio
|
|
5
|
+
# All rights reserved.
|
|
6
|
+
#
|
|
7
|
+
# Redistribution and use in source and binary forms, with or without
|
|
8
|
+
# modification, are permitted provided that the following conditions are met:
|
|
9
|
+
#
|
|
10
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
|
11
|
+
# list of conditions and the following disclaimer.
|
|
12
|
+
#
|
|
13
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
# and/or other materials provided with the distribution.
|
|
16
|
+
#
|
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLD
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import platform
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from functools import lru_cache
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Optional, no_type_check
|
|
29
|
+
|
|
30
|
+
FNAME_PER_PLATFORM = {
|
|
31
|
+
"macos-aarch64": "ffmpeg-macos-aarch64-v7.1",
|
|
32
|
+
"macos-x86_64": "ffmpeg-macos-x86_64-v7.1",
|
|
33
|
+
"windows-x86_64": "ffmpeg-win-x86_64-v7.1.exe",
|
|
34
|
+
"windows-i686": "ffmpeg-win32-v4.2.2.exe",
|
|
35
|
+
"linux-aarch64": "ffmpeg-linux-aarch64-v7.0.2",
|
|
36
|
+
"linux-x86_64": "ffmpeg-linux-x86_64-v7.0.2",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_os_string() -> str:
|
|
41
|
+
if sys.platform.startswith("win"):
|
|
42
|
+
return "windows"
|
|
43
|
+
elif sys.platform.startswith("darwin"):
|
|
44
|
+
return "macos"
|
|
45
|
+
elif sys.platform.startswith("linux"):
|
|
46
|
+
return "linux"
|
|
47
|
+
return sys.platform
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_arch() -> str:
|
|
51
|
+
is_64_bit = sys.maxsize > 2**32
|
|
52
|
+
machine = platform.machine()
|
|
53
|
+
|
|
54
|
+
if machine == "armv7l":
|
|
55
|
+
return "armv7"
|
|
56
|
+
elif is_64_bit and machine.startswith(("arm", "aarch64")):
|
|
57
|
+
return "aarch64"
|
|
58
|
+
elif is_64_bit:
|
|
59
|
+
return "x86_64"
|
|
60
|
+
return "i686"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _get_platform() -> str:
|
|
64
|
+
return _get_os_string() + "-" + _get_arch()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@no_type_check
|
|
68
|
+
def _popen_kwargs() -> dict[str, Any]:
|
|
69
|
+
startupinfo = None
|
|
70
|
+
creationflags = 0
|
|
71
|
+
if sys.platform.startswith("win"):
|
|
72
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
73
|
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
74
|
+
return {
|
|
75
|
+
"startupinfo": startupinfo,
|
|
76
|
+
"creationflags": creationflags,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_valid_exe(exe: str) -> bool:
|
|
81
|
+
cmd = [exe, "-version"]
|
|
82
|
+
try:
|
|
83
|
+
with open(os.devnull, "w") as null:
|
|
84
|
+
subprocess.check_call(
|
|
85
|
+
cmd, stdout=null, stderr=subprocess.STDOUT, **_popen_kwargs()
|
|
86
|
+
)
|
|
87
|
+
return True
|
|
88
|
+
except (OSError, ValueError, subprocess.CalledProcessError):
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@lru_cache(maxsize=1)
|
|
93
|
+
def _find_ffmpeg_path() -> Optional[Path]:
|
|
94
|
+
plat = _get_platform()
|
|
95
|
+
os_str = _get_os_string()
|
|
96
|
+
expected_filename = FNAME_PER_PLATFORM.get(plat, "ffmpeg")
|
|
97
|
+
|
|
98
|
+
possible_paths: list[Path] = []
|
|
99
|
+
home = Path.home()
|
|
100
|
+
|
|
101
|
+
imageio_cache_dir = home / ".imageio" / "ffmpeg"
|
|
102
|
+
desklab_cache_dir = home / ".desklab" / "ffmpeg"
|
|
103
|
+
|
|
104
|
+
possible_paths.extend([
|
|
105
|
+
imageio_cache_dir / expected_filename,
|
|
106
|
+
desklab_cache_dir / expected_filename,
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
prefix_path = Path(sys.prefix)
|
|
110
|
+
if os_str == "windows":
|
|
111
|
+
possible_paths.append(prefix_path / "Library" / "bin" / "ffmpeg.exe")
|
|
112
|
+
else:
|
|
113
|
+
possible_paths.append(prefix_path / "bin" / "ffmpeg")
|
|
114
|
+
|
|
115
|
+
if os_str == "windows":
|
|
116
|
+
appdata_local = Path(os.environ.get(
|
|
117
|
+
"LOCALAPPDATA", home / "AppData/Local"))
|
|
118
|
+
possible_paths.extend([
|
|
119
|
+
appdata_local / "Microsoft/WindowsApps/ffmpeg.exe",
|
|
120
|
+
home / "ffmpeg" / "bin" / "ffmpeg.exe",
|
|
121
|
+
Path("C:/Program Files/ffmpeg/bin/ffmpeg.exe"),
|
|
122
|
+
Path("C:/ffmpeg/bin/ffmpeg.exe"),
|
|
123
|
+
])
|
|
124
|
+
elif os_str == "macos":
|
|
125
|
+
possible_paths.extend([
|
|
126
|
+
Path("/opt/homebrew/bin/ffmpeg"),
|
|
127
|
+
Path("/usr/local/bin/ffmpeg"),
|
|
128
|
+
Path("/usr/bin/ffmpeg"),
|
|
129
|
+
home / "bin" / "ffmpeg",
|
|
130
|
+
])
|
|
131
|
+
elif os_str == "linux":
|
|
132
|
+
possible_paths.extend([
|
|
133
|
+
Path("/usr/bin/ffmpeg"),
|
|
134
|
+
Path("/usr/local/bin/ffmpeg"),
|
|
135
|
+
Path("/snap/bin/ffmpeg"),
|
|
136
|
+
home / "bin" / "ffmpeg",
|
|
137
|
+
home / ".local" / "bin" / "ffmpeg",
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
for path in possible_paths:
|
|
141
|
+
if path.is_file() and _is_valid_exe(str(path)):
|
|
142
|
+
return path
|
|
143
|
+
|
|
144
|
+
if _is_valid_exe("ffmpeg"):
|
|
145
|
+
return Path("ffmpeg")
|
|
146
|
+
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_ffmpeg_exe() -> Path:
|
|
151
|
+
exe_env = os.getenv("DESKLAB_FFMPEG_EXE") or os.getenv(
|
|
152
|
+
"IMAGEIO_FFMPEG_EXE")
|
|
153
|
+
if exe_env:
|
|
154
|
+
return Path(exe_env)
|
|
155
|
+
|
|
156
|
+
detected_path = _find_ffmpeg_path()
|
|
157
|
+
if detected_path:
|
|
158
|
+
return detected_path
|
|
159
|
+
|
|
160
|
+
raise RuntimeError(
|
|
161
|
+
"No ffmpeg exe could be found. Install ffmpeg on your system, "
|
|
162
|
+
"or set the DESKLAB_FFMPEG_EXE environment variable."
|
|
163
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from desklab.entity_types import ContainableEntity, DisplayableEntity, CopiableEntity, ColorableEntity
|
|
2
|
+
from desklab.primitives import Color
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AreaInterface(ContainableEntity, DisplayableEntity, ColorableEntity, CopiableEntity):
|
|
7
|
+
|
|
8
|
+
def __init__(self, width: int, height: int, color: Color | tuple[int, ...] | str = "BLACK") -> None:
|
|
9
|
+
super().__init__(x=0, y=0, width=width, height=height, color=color)
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def contains(self, coordinates: tuple[int, int]) -> bool:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def get_rect(self) -> tuple[int, int, int, int]:
|
|
16
|
+
return (self.get_x(), self.get_y(),
|
|
17
|
+
self.get_width(), self.get_height())
|