pyside6stylekit 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Noritama-Lab
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,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyside6stylekit
3
+ Version: 0.1.0
4
+ Summary: A lightweight and extensible styled UI component kit for PySide6 applications.
5
+ Author: Noritama-Lab
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/Noritama-Lab/PySide6StyleKit
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Software Development :: User Interfaces
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: PySide6>=6.0.0
24
+ Dynamic: license-file
25
+
26
+ # PySide6StyleKit
27
+ PySide6 アプリのための軽量で拡張性の高いスタイル済み UI コンポーネントキット。
28
+ A lightweight and extensible styled UI component kit for PySide6 applications.
29
+
30
+ ---
31
+
32
+ ## 特徴 / Features
33
+ - 統一テーマ(ライト/ダーク/背景色)
34
+ Unified themes (light, dark, background-aware)
35
+ - スタイル済みウィジェット(Label / LineEdit / Button / Indus Alternate Button / Indus Lamp / Indus Momentary Button など)
36
+ Styled widgets such as Label, LineEdit, Button, Indus Alternate Button, Indus Lamp, and Indus Momentary Button
37
+ - 数値バリデーション(int / float / range)
38
+ Robust numeric validation (int, float, range)
39
+ - エラー表示の柔軟な制御(枠線のみ・メッセージ表示)
40
+ Flexible error signaling (border-only or message)
41
+ - 拡張しやすいモジュール構造
42
+ Modular and extensible architecture
43
+ - 実用的なサンプルコード付き
44
+ Includes practical example scripts
45
+
46
+ ---
47
+
48
+ ## インストール / Installation
49
+ 開発モードで利用する場合:
50
+ For development mode:
51
+
52
+ ```bash
53
+ pip install -e .
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 使い方 / Usage
59
+ `examples/demo.py` と `examples/demo_indus.py` に基本的な使い方があります。
60
+ Basic usage is available in `examples/demo.py` and `examples/demo_indus.py`.
61
+
62
+ ```python
63
+ from pyside6stylekit import Theme, StyledLabel, StyledLineEdit, StyledButton
64
+
65
+ theme = Theme.light()
66
+
67
+ label = StyledLabel("Hello")
68
+ lineedit = StyledLineEdit(validator="int")
69
+ button = StyledButton("Submit")
70
+ ```
71
+
72
+ ---
73
+
74
+ ## プロジェクト構造 / Project Structure
75
+ ```
76
+ pyside6stylekit/
77
+ ├─ pyside6stylekit/
78
+ │ ├─ utils/
79
+ │ │ ├─ __init__.py
80
+ │ │ └─ colors.py
81
+ │ ├─ widgets/
82
+ │ │ ├─ __init__.py
83
+ │ │ ├─ button.py
84
+ │ │ ├─ indus_alternate_button.py
85
+ │ │ ├─ indus_lamp.py
86
+ │ │ ├─ indus_momentary_button.py
87
+ │ │ ├─ label.py
88
+ │ │ └─ lineedit.py
89
+ │ ├─ __init__.py
90
+ │ ├─ presets.py
91
+ │ └─ theme.py
92
+ ├─ examples/
93
+ │ ├─ demo.py
94
+ │ └─ demo_indus.py
95
+ ├─ LICENSE
96
+ └─ README.md
97
+ ```
98
+
99
+ ---
100
+
101
+ ## ライセンス / License
102
+ MIT License © 2026 Mitsunori
103
+
104
+ ---
105
+
106
+ ## 作者 / Author
107
+ Noritama-Lab
@@ -0,0 +1,82 @@
1
+ # PySide6StyleKit
2
+ PySide6 アプリのための軽量で拡張性の高いスタイル済み UI コンポーネントキット。
3
+ A lightweight and extensible styled UI component kit for PySide6 applications.
4
+
5
+ ---
6
+
7
+ ## 特徴 / Features
8
+ - 統一テーマ(ライト/ダーク/背景色)
9
+ Unified themes (light, dark, background-aware)
10
+ - スタイル済みウィジェット(Label / LineEdit / Button / Indus Alternate Button / Indus Lamp / Indus Momentary Button など)
11
+ Styled widgets such as Label, LineEdit, Button, Indus Alternate Button, Indus Lamp, and Indus Momentary Button
12
+ - 数値バリデーション(int / float / range)
13
+ Robust numeric validation (int, float, range)
14
+ - エラー表示の柔軟な制御(枠線のみ・メッセージ表示)
15
+ Flexible error signaling (border-only or message)
16
+ - 拡張しやすいモジュール構造
17
+ Modular and extensible architecture
18
+ - 実用的なサンプルコード付き
19
+ Includes practical example scripts
20
+
21
+ ---
22
+
23
+ ## インストール / Installation
24
+ 開発モードで利用する場合:
25
+ For development mode:
26
+
27
+ ```bash
28
+ pip install -e .
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 使い方 / Usage
34
+ `examples/demo.py` と `examples/demo_indus.py` に基本的な使い方があります。
35
+ Basic usage is available in `examples/demo.py` and `examples/demo_indus.py`.
36
+
37
+ ```python
38
+ from pyside6stylekit import Theme, StyledLabel, StyledLineEdit, StyledButton
39
+
40
+ theme = Theme.light()
41
+
42
+ label = StyledLabel("Hello")
43
+ lineedit = StyledLineEdit(validator="int")
44
+ button = StyledButton("Submit")
45
+ ```
46
+
47
+ ---
48
+
49
+ ## プロジェクト構造 / Project Structure
50
+ ```
51
+ pyside6stylekit/
52
+ ├─ pyside6stylekit/
53
+ │ ├─ utils/
54
+ │ │ ├─ __init__.py
55
+ │ │ └─ colors.py
56
+ │ ├─ widgets/
57
+ │ │ ├─ __init__.py
58
+ │ │ ├─ button.py
59
+ │ │ ├─ indus_alternate_button.py
60
+ │ │ ├─ indus_lamp.py
61
+ │ │ ├─ indus_momentary_button.py
62
+ │ │ ├─ label.py
63
+ │ │ └─ lineedit.py
64
+ │ ├─ __init__.py
65
+ │ ├─ presets.py
66
+ │ └─ theme.py
67
+ ├─ examples/
68
+ │ ├─ demo.py
69
+ │ └─ demo_indus.py
70
+ ├─ LICENSE
71
+ └─ README.md
72
+ ```
73
+
74
+ ---
75
+
76
+ ## ライセンス / License
77
+ MIT License © 2026 Mitsunori
78
+
79
+ ---
80
+
81
+ ## 作者 / Author
82
+ Noritama-Lab
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pyside6stylekit"
7
+ version = "0.1.0"
8
+ description = "A lightweight and extensible styled UI component kit for PySide6 applications."
9
+ authors = [
10
+ {name = "Noritama-Lab"}
11
+ ]
12
+ license = {text = "MIT"}
13
+ dependencies = [
14
+ "PySide6>=6.0.0"
15
+ ]
16
+ readme = "README.md"
17
+ requires-python = ">=3.8"
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.8",
25
+ "Programming Language :: Python :: 3.9",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ "Topic :: Software Development :: User Interfaces",
31
+ ]
32
+
33
+ [project.urls]
34
+ Repository = "https://github.com/Noritama-Lab/PySide6StyleKit"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["."]
38
+ exclude = ["examples*", "tests*"]
@@ -0,0 +1,16 @@
1
+ from .theme import Theme
2
+ from .presets import SIZE_PRESETS
3
+ from .widgets import (
4
+ StyledLabel, StyledButton, StyledLineEdit,
5
+ IndusMomentaryButton, IndusAlternateButton, IndusLamp
6
+ )
7
+ __all__ = [
8
+ "Theme",
9
+ "SIZE_PRESETS",
10
+ "StyledLabel",
11
+ "StyledButton",
12
+ "StyledLineEdit",
13
+ "IndusMomentaryButton",
14
+ "IndusAlternateButton",
15
+ "IndusLamp",
16
+ ]
@@ -0,0 +1,6 @@
1
+ # presets.py
2
+ SIZE_PRESETS = {
3
+ "small": {"font": 11, "padding": (6, 10)},
4
+ "mid": {"font": 13, "padding": (8, 14)},
5
+ "large": {"font": 16, "padding": (12, 18)},
6
+ }
@@ -0,0 +1,71 @@
1
+ # theme.py
2
+ from .presets import SIZE_PRESETS
3
+ from .utils.colors import normalize_color, adjust_color
4
+
5
+
6
+ class Theme:
7
+ def __init__(self, primary="blue", mode="light",
8
+ font_family="Segoe UI", size="mid",
9
+ text_color=None):
10
+ self.primary = normalize_color(primary)
11
+ self.mode = mode
12
+ self.font_family = font_family
13
+ self.size = size
14
+
15
+ if size not in SIZE_PRESETS:
16
+ raise ValueError("size must be small / mid / large")
17
+
18
+ # -------------------------
19
+ # 文字色(ユーザー指定 > モードデフォルト)
20
+ # -------------------------
21
+ if text_color:
22
+ self.text_color = normalize_color(text_color)
23
+ else:
24
+ if mode == "light":
25
+ self.text_color = "#000000"
26
+ elif mode == "dark":
27
+ self.text_color = "#ffffff"
28
+ else:
29
+ raise ValueError("mode must be light / dark")
30
+
31
+ # 背景・枠色
32
+ if mode == "light":
33
+ self.background = "#ffffff"
34
+ self.border_color = "#dcdde1"
35
+ else:
36
+ self.background = "#2f3640"
37
+ self.border_color = "#4b4b4b"
38
+
39
+ # -------------------------
40
+ # 色のバリエーション
41
+ # -------------------------
42
+ def primary_dark(self, factor=0.5):
43
+ """OFF の時に使う暗い色"""
44
+ return adjust_color(self.primary, factor)
45
+
46
+ def hover(self):
47
+ return adjust_color(self.primary, 0.85)
48
+
49
+ def pressed(self):
50
+ return adjust_color(self.primary, 0.70)
51
+
52
+ # -------------------------
53
+ # サイズ
54
+ # -------------------------
55
+ def font_size(self):
56
+ return SIZE_PRESETS[self.size]["font"]
57
+
58
+ def padding(self):
59
+ return SIZE_PRESETS[self.size]["padding"]
60
+
61
+ # -------------------------
62
+ # QSS 出力
63
+ # -------------------------
64
+ def export_qss(self):
65
+ return f"""
66
+ * {{
67
+ font-family: '{self.font_family}';
68
+ color: {self.text_color};
69
+ background: {self.background};
70
+ }}
71
+ """
@@ -0,0 +1,6 @@
1
+ from .colors import normalize_color, adjust_color
2
+
3
+ __all__ = [
4
+ "normalize_color",
5
+ "adjust_color",
6
+ ]
@@ -0,0 +1,33 @@
1
+ # utils/colors.py
2
+ def normalize_color(color):
3
+ css_colors = {
4
+ "red": "#ff0000", "green": "#00ff00", "blue": "#0000ff",
5
+ "black": "#000000", "white": "#ffffff", "gray": "#808080",
6
+ "orange": "#ffa500", "yellow": "#ffff00", "purple": "#800080",
7
+ "pink": "#ffc0cb", "sky": "#87ceeb",
8
+ }
9
+
10
+ if isinstance(color, tuple) and len(color) == 3:
11
+ r, g, b = color
12
+ return f"#{r:02x}{g:02x}{b:02x}"
13
+
14
+ if isinstance(color, str):
15
+ if color.startswith("#") and len(color) == 7:
16
+ return color.lower()
17
+ if color.lower() in css_colors:
18
+ return css_colors[color.lower()]
19
+
20
+ raise ValueError(f"Invalid color: {color}")
21
+
22
+
23
+ def adjust_color(color, factor):
24
+ color = color.lstrip("#")
25
+ r = int(color[0:2], 16)
26
+ g = int(color[2:4], 16)
27
+ b = int(color[4:6], 16)
28
+
29
+ r = max(0, min(255, int(r * factor)))
30
+ g = max(0, min(255, int(g * factor)))
31
+ b = max(0, min(255, int(b * factor)))
32
+
33
+ return f"#{r:02x}{g:02x}{b:02x}"
@@ -0,0 +1,15 @@
1
+ from .label import StyledLabel
2
+ from .button import StyledButton
3
+ from .lineedit import StyledLineEdit
4
+ from .indus_momentary_button import IndusMomentaryButton
5
+ from .indus_alternate_button import IndusAlternateButton
6
+ from .indus_lamp import IndusLamp
7
+
8
+ __all__ = [
9
+ "StyledLabel",
10
+ "StyledButton",
11
+ "StyledLineEdit",
12
+ "IndusMomentaryButton",
13
+ "IndusAlternateButton",
14
+ "IndusLamp",
15
+ ]
@@ -0,0 +1,44 @@
1
+ # widgets/button.py
2
+ from PySide6.QtWidgets import QPushButton
3
+ from PySide6.QtGui import QFont, QIcon
4
+
5
+ class StyledButton(QPushButton):
6
+ def __init__(self, text, theme, icon_path=None, text_color=None):
7
+ super().__init__(text)
8
+ self.theme = theme
9
+ self.text_color = text_color or theme.text_color
10
+
11
+ self.setFont(QFont(theme.font_family, theme.font_size()))
12
+
13
+ if icon_path:
14
+ self.setIcon(QIcon(icon_path))
15
+
16
+ self.apply_style()
17
+
18
+ def apply_style(self):
19
+ pad_v, pad_h = self.theme.padding()
20
+
21
+ bg = self.theme.primary
22
+ hover = self.theme.hover()
23
+ pressed = self.theme.pressed()
24
+
25
+ self.setStyleSheet(f"""
26
+ QPushButton {{
27
+ background-color: {bg};
28
+ color: {self.text_color};
29
+ padding: {pad_v}px {pad_h}px;
30
+ border-radius: 6px;
31
+ font-family: '{self.theme.font_family}';
32
+ border: none;
33
+ }}
34
+ QPushButton:hover {{
35
+ background-color: {hover};
36
+ }}
37
+ QPushButton:pressed {{
38
+ background-color: {pressed};
39
+ }}
40
+ QPushButton:disabled {{
41
+ background-color: #999;
42
+ color: #666;
43
+ }}
44
+ """)
@@ -0,0 +1,60 @@
1
+ # widgets/indus_alternate_button.py
2
+ from PySide6.QtWidgets import QPushButton
3
+ from PySide6.QtGui import QFont, QIcon, QColor
4
+
5
+ def darken(hex_color: str, factor: float = 0.7) -> str:
6
+ c = QColor(hex_color)
7
+ r = int(c.red() * factor)
8
+ g = int(c.green() * factor)
9
+ b = int(c.blue() * factor)
10
+ return f"#{r:02x}{g:02x}{b:02x}"
11
+
12
+ class IndusAlternateButton(QPushButton):
13
+ def __init__(self, text, theme, icon_path=None, diameter=48, text_color=None):
14
+ super().__init__(text)
15
+ self.theme = theme
16
+ self.diameter = diameter
17
+ self.text_color = text_color or theme.text_color
18
+
19
+ self.setCheckable(True)
20
+ self.setFixedSize(diameter, diameter)
21
+ self.setFont(QFont(theme.font_family, theme.font_size()))
22
+
23
+ if icon_path:
24
+ self.setIcon(QIcon(icon_path))
25
+
26
+ self.apply_style()
27
+ self.toggled.connect(self.apply_style)
28
+
29
+ def apply_style(self):
30
+ if self.isChecked():
31
+ bg = self.theme.primary
32
+ hover = self.theme.hover()
33
+ pressed = self.theme.pressed()
34
+ else:
35
+ bg = self.theme.primary_dark(0.5)
36
+ hover = self.theme.primary_dark(0.6)
37
+ pressed = self.theme.primary_dark(0.4)
38
+
39
+ border_color = darken(self.theme.primary, 0.8)
40
+
41
+ self.setStyleSheet(f"""
42
+ QPushButton {{
43
+ background-color: {bg};
44
+ color: {self.text_color};
45
+ border-radius: {self.diameter // 2}px;
46
+ border: 2px solid {border_color};
47
+ font-family: '{self.theme.font_family}';
48
+ font-weight: bold;
49
+ }}
50
+ QPushButton:hover {{
51
+ background-color: {hover};
52
+ }}
53
+ QPushButton:pressed {{
54
+ background-color: {pressed};
55
+ }}
56
+ QPushButton:disabled {{
57
+ background-color: #999;
58
+ color: #666;
59
+ }}
60
+ """)
@@ -0,0 +1,53 @@
1
+ # widgets/indus_lamp.py
2
+ from PySide6.QtWidgets import QLabel
3
+ from PySide6.QtGui import QFont
4
+ from PySide6.QtCore import Qt
5
+
6
+
7
+ class IndusLamp(QLabel):
8
+ def __init__(self, text, theme, diameter=48,
9
+ state=False, text_color=None):
10
+ super().__init__(text)
11
+
12
+ self.theme = theme
13
+ self.diameter = diameter
14
+ self.state = state
15
+ self.text_color = text_color or theme.text_color
16
+
17
+ self.setFixedSize(diameter, diameter)
18
+ self.setAlignment(Qt.AlignCenter)
19
+ self.setFont(QFont(theme.font_family, theme.font_size()))
20
+
21
+ self.apply_style()
22
+
23
+ # -------------------------
24
+ # 状態変更
25
+ # -------------------------
26
+ def set_state(self, state: bool):
27
+ self.state = state
28
+ self.apply_style()
29
+
30
+ # -------------------------
31
+ # 色適用(丸ボタンと完全一致)
32
+ # -------------------------
33
+ def apply_style(self):
34
+ if not self.isEnabled():
35
+ bg = "#999"
36
+ fg = "#666"
37
+ else:
38
+ if self.state:
39
+ bg = self.theme.primary
40
+ else:
41
+ bg = self.theme.primary_dark(0.5)
42
+ fg = self.text_color
43
+
44
+ self.setStyleSheet(f"""
45
+ QLabel {{
46
+ background-color: {bg};
47
+ color: {fg};
48
+ border-radius: {self.diameter // 2}px;
49
+ border: none;
50
+ font-family: '{self.theme.font_family}';
51
+ font-weight: bold;
52
+ }}
53
+ """)
@@ -0,0 +1,53 @@
1
+ # widgets/indus_momentary_button.py
2
+ from PySide6.QtWidgets import QPushButton
3
+ from PySide6.QtGui import QFont, QIcon, QColor
4
+
5
+ def darken(hex_color: str, factor: float = 0.7) -> str:
6
+ c = QColor(hex_color)
7
+ r = int(c.red() * factor)
8
+ g = int(c.green() * factor)
9
+ b = int(c.blue() * factor)
10
+ return f"#{r:02x}{g:02x}{b:02x}"
11
+
12
+ class IndusMomentaryButton(QPushButton):
13
+ def __init__(self, text, theme, icon_path=None, diameter=48, text_color=None):
14
+ super().__init__(text)
15
+ self.theme = theme
16
+ self.diameter = diameter
17
+ self.text_color = text_color or theme.text_color
18
+
19
+ self.setCheckable(False)
20
+ self.setFixedSize(diameter, diameter)
21
+ self.setFont(QFont(theme.font_family, theme.font_size()))
22
+
23
+ if icon_path:
24
+ self.setIcon(QIcon(icon_path))
25
+
26
+ self.apply_style()
27
+
28
+ def apply_style(self):
29
+ bg = darken(self.theme.primary, 0.5)
30
+ hover = darken(self.theme.primary, 0.6)
31
+ pressed = self.theme.primary
32
+ border_color = darken(self.theme.primary, 0.8)
33
+
34
+ self.setStyleSheet(f"""
35
+ QPushButton {{
36
+ background-color: {bg};
37
+ color: {self.text_color};
38
+ border-radius: {self.diameter // 2}px;
39
+ border: 2px solid {border_color};
40
+ font-family: '{self.theme.font_family}';
41
+ font-weight: bold;
42
+ }}
43
+ QPushButton:hover {{
44
+ background-color: {hover};
45
+ }}
46
+ QPushButton:pressed {{
47
+ background-color: {pressed};
48
+ }}
49
+ QPushButton:disabled {{
50
+ background-color: #999;
51
+ color: #666;
52
+ }}
53
+ """)
@@ -0,0 +1,25 @@
1
+ # widgets/label.py
2
+ from PySide6.QtWidgets import QLabel
3
+ from PySide6.QtGui import QFont
4
+
5
+ class StyledLabel(QLabel):
6
+ def __init__(self, text, theme, bold=False,
7
+ text_color=None, background_color=None):
8
+ super().__init__(text)
9
+
10
+ weight = QFont.Bold if bold else QFont.Normal
11
+ self.setFont(QFont(theme.font_family, theme.font_size(), weight))
12
+
13
+ # 文字色(個別指定 > theme)
14
+ color = text_color or theme.text_color
15
+
16
+ # 背景色(個別指定 > transparent)
17
+ bg = background_color or "transparent"
18
+
19
+ self.setStyleSheet(f"""
20
+ QLabel {{
21
+ color: {color};
22
+ background: {bg};
23
+ font-family: '{theme.font_family}';
24
+ }}
25
+ """)
@@ -0,0 +1,128 @@
1
+ # widgets/lineedit.py
2
+ from PySide6.QtWidgets import QWidget, QLineEdit, QLabel, QVBoxLayout, QToolTip
3
+ from PySide6.QtGui import QFont, QRegularExpressionValidator, QIntValidator, QDoubleValidator
4
+ from PySide6.QtCore import QRegularExpression
5
+
6
+ class StyledLineEdit(QWidget):
7
+ def __init__(self, placeholder, theme, mode="free",
8
+ min_val=None, max_val=None):
9
+ super().__init__()
10
+
11
+ self.theme = theme
12
+ self.mode = mode
13
+ self.min_val = min_val
14
+ self.max_val = max_val
15
+
16
+ self.edit = QLineEdit()
17
+ self.error_label = QLabel(" ")
18
+ self.error_label.setFixedHeight(14)
19
+ self.error_label.setStyleSheet("color: transparent; font-size: 11px;")
20
+
21
+ layout = QVBoxLayout(self)
22
+ layout.setContentsMargins(0, 0, 0, 0)
23
+ layout.addWidget(self.edit)
24
+ layout.addWidget(self.error_label)
25
+
26
+ self.edit.setPlaceholderText(placeholder)
27
+ self.edit.setFont(QFont(theme.font_family, theme.font_size()))
28
+
29
+ self.apply_style()
30
+ self.apply_validator(mode, min_val, max_val)
31
+
32
+ def apply_style(self, error=False):
33
+ pad_v, pad_h = self.theme.padding()
34
+ border_color = "red" if error else self.theme.border_color
35
+
36
+ self.edit.setStyleSheet(f"""
37
+ QLineEdit {{
38
+ padding: {pad_v}px {pad_h}px;
39
+ border: 2px solid {border_color};
40
+ border-radius: 6px;
41
+ background: {self.theme.background};
42
+ color: {self.theme.text_color};
43
+ font-family: '{self.theme.font_family}';
44
+ }}
45
+ QLineEdit:focus {{
46
+ border: 2px solid {self.theme.primary};
47
+ }}
48
+ """)
49
+
50
+ def apply_validator(self, mode, min_val, max_val):
51
+ if mode == "free":
52
+ return
53
+
54
+ if mode == "numeric":
55
+ regex = QRegularExpression(r"^[0-9]+$")
56
+ self.edit.setValidator(QRegularExpressionValidator(regex))
57
+ return
58
+
59
+ if mode == "numeric_range":
60
+ self._apply_numeric_range(min_val, max_val)
61
+ return
62
+
63
+ if mode == "alnum":
64
+ regex = QRegularExpression(r"^[A-Za-z0-9]+$")
65
+ self.edit.setValidator(QRegularExpressionValidator(regex))
66
+ return
67
+
68
+ if mode == "filename":
69
+ regex = QRegularExpression(r'^[^\\/:*?"<>|]+$')
70
+ self.edit.setValidator(QRegularExpressionValidator(regex))
71
+ return
72
+
73
+ raise ValueError(f"Unknown mode: {mode}")
74
+
75
+ def _apply_numeric_range(self, min_val, max_val):
76
+ if min_val is None or max_val is None:
77
+ raise ValueError("numeric_range requires min_val and max_val")
78
+
79
+ is_int = isinstance(min_val, int) and isinstance(max_val, int)
80
+
81
+ if is_int:
82
+ validator = QIntValidator(min_val, max_val)
83
+ else:
84
+ decimals = max(self._decimal_places(min_val),
85
+ self._decimal_places(max_val))
86
+ validator = QDoubleValidator(min_val, max_val, decimals)
87
+ validator.setNotation(QDoubleValidator.StandardNotation)
88
+
89
+ self.edit.setValidator(validator)
90
+
91
+ def _decimal_places(self, value):
92
+ s = str(value)
93
+ return len(s.split(".")[1]) if "." in s else 0
94
+
95
+ def value(self):
96
+ try:
97
+ return float(self.edit.text())
98
+ except ValueError:
99
+ return None
100
+
101
+ def is_valid(self):
102
+ if self.mode != "numeric_range":
103
+ return True
104
+
105
+ v = self.value()
106
+ if v is None:
107
+ return False
108
+
109
+ return self.min_val <= v <= self.max_val
110
+
111
+ def show_error(self, message=None):
112
+ if message is None:
113
+ self.error_label.setText(" ")
114
+ self.error_label.setStyleSheet("color: transparent; font-size: 11px;")
115
+ self.apply_style(error=True)
116
+ return
117
+
118
+ if message == "":
119
+ self.error_label.setText(" ")
120
+ self.error_label.setStyleSheet("color: transparent; font-size: 11px;")
121
+ self.apply_style(error=False)
122
+ return
123
+
124
+ QToolTip.showText(self.edit.mapToGlobal(self.edit.rect().bottomLeft()), message)
125
+
126
+ self.error_label.setText(" ")
127
+ self.error_label.setStyleSheet("color: transparent; font-size: 11px;")
128
+ self.apply_style(error=True)
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyside6stylekit
3
+ Version: 0.1.0
4
+ Summary: A lightweight and extensible styled UI component kit for PySide6 applications.
5
+ Author: Noritama-Lab
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/Noritama-Lab/PySide6StyleKit
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Software Development :: User Interfaces
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: PySide6>=6.0.0
24
+ Dynamic: license-file
25
+
26
+ # PySide6StyleKit
27
+ PySide6 アプリのための軽量で拡張性の高いスタイル済み UI コンポーネントキット。
28
+ A lightweight and extensible styled UI component kit for PySide6 applications.
29
+
30
+ ---
31
+
32
+ ## 特徴 / Features
33
+ - 統一テーマ(ライト/ダーク/背景色)
34
+ Unified themes (light, dark, background-aware)
35
+ - スタイル済みウィジェット(Label / LineEdit / Button / Indus Alternate Button / Indus Lamp / Indus Momentary Button など)
36
+ Styled widgets such as Label, LineEdit, Button, Indus Alternate Button, Indus Lamp, and Indus Momentary Button
37
+ - 数値バリデーション(int / float / range)
38
+ Robust numeric validation (int, float, range)
39
+ - エラー表示の柔軟な制御(枠線のみ・メッセージ表示)
40
+ Flexible error signaling (border-only or message)
41
+ - 拡張しやすいモジュール構造
42
+ Modular and extensible architecture
43
+ - 実用的なサンプルコード付き
44
+ Includes practical example scripts
45
+
46
+ ---
47
+
48
+ ## インストール / Installation
49
+ 開発モードで利用する場合:
50
+ For development mode:
51
+
52
+ ```bash
53
+ pip install -e .
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 使い方 / Usage
59
+ `examples/demo.py` と `examples/demo_indus.py` に基本的な使い方があります。
60
+ Basic usage is available in `examples/demo.py` and `examples/demo_indus.py`.
61
+
62
+ ```python
63
+ from pyside6stylekit import Theme, StyledLabel, StyledLineEdit, StyledButton
64
+
65
+ theme = Theme.light()
66
+
67
+ label = StyledLabel("Hello")
68
+ lineedit = StyledLineEdit(validator="int")
69
+ button = StyledButton("Submit")
70
+ ```
71
+
72
+ ---
73
+
74
+ ## プロジェクト構造 / Project Structure
75
+ ```
76
+ pyside6stylekit/
77
+ ├─ pyside6stylekit/
78
+ │ ├─ utils/
79
+ │ │ ├─ __init__.py
80
+ │ │ └─ colors.py
81
+ │ ├─ widgets/
82
+ │ │ ├─ __init__.py
83
+ │ │ ├─ button.py
84
+ │ │ ├─ indus_alternate_button.py
85
+ │ │ ├─ indus_lamp.py
86
+ │ │ ├─ indus_momentary_button.py
87
+ │ │ ├─ label.py
88
+ │ │ └─ lineedit.py
89
+ │ ├─ __init__.py
90
+ │ ├─ presets.py
91
+ │ └─ theme.py
92
+ ├─ examples/
93
+ │ ├─ demo.py
94
+ │ └─ demo_indus.py
95
+ ├─ LICENSE
96
+ └─ README.md
97
+ ```
98
+
99
+ ---
100
+
101
+ ## ライセンス / License
102
+ MIT License © 2026 Mitsunori
103
+
104
+ ---
105
+
106
+ ## 作者 / Author
107
+ Noritama-Lab
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ pyside6stylekit/__init__.py
5
+ pyside6stylekit/presets.py
6
+ pyside6stylekit/theme.py
7
+ pyside6stylekit.egg-info/PKG-INFO
8
+ pyside6stylekit.egg-info/SOURCES.txt
9
+ pyside6stylekit.egg-info/dependency_links.txt
10
+ pyside6stylekit.egg-info/requires.txt
11
+ pyside6stylekit.egg-info/top_level.txt
12
+ pyside6stylekit/utils/__init__.py
13
+ pyside6stylekit/utils/colors.py
14
+ pyside6stylekit/widgets/__init__.py
15
+ pyside6stylekit/widgets/button.py
16
+ pyside6stylekit/widgets/indus_alternate_button.py
17
+ pyside6stylekit/widgets/indus_lamp.py
18
+ pyside6stylekit/widgets/indus_momentary_button.py
19
+ pyside6stylekit/widgets/label.py
20
+ pyside6stylekit/widgets/lineedit.py
@@ -0,0 +1 @@
1
+ PySide6>=6.0.0
@@ -0,0 +1,2 @@
1
+ dist
2
+ pyside6stylekit
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+