| Name | ui-fram-skk JSON |
| Version |
0.1.1
JSON |
| download |
| home_page | None |
| Summary | Framework de UI desktop (PySide6) com janela frameless, roteamento e temas |
| upload_time | 2025-10-24 19:29:03 |
| maintainer | None |
| docs_url | None |
| author | None |
| requires_python | >=3.10 |
| license | Copyright 2025 João Vitor de Souza Siqueira
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| keywords |
pyside6
qt
ui
desktop
framework
|
| VCS |
 |
| bugtrack_url |
|
| requirements |
PySide6
|
| Travis-CI |
No Travis.
|
| coveralls test coverage |
No coveralls.
|
<p align="center">
<img src="app/assets/icons/app/app.ico" width="96" alt="App Icon" />
</p>
<p align="center">
<a href="README.en.md">Read this in English</a>
</p>
---
# UI Exec Framework (PySide6)
Base moderna para aplicativos desktop com Python + PySide6. Inclui janela frameless, roteamento hierárquico, sistema de temas (QSS + JSON) com animação, toasts e Centro de Notificações, além de um CLI para produtividade.
---
## Sumário
- [Visão Geral](#visão-geral)
- [Instalacao (PyPI) e uso rapido](#instalacao-pypi-e-uso-rapido)
- [Arquitetura](#arquitetura)
- [Instalação e Setup](#instalação-e-setup)
- [Execução e Atalhos](#execução-e-atalhos)
- [Páginas e Roteamento](#páginas-e-roteamento)
- [Manifesto](#manifesto-appassetspages_manifestjson)
- [Auto‑descoberta](#auto‑descoberta-apppages)
- [Injeção de dependências](#injeção-de-dependências)
- [Recursos do Router](#recursos-do-router)
- [Temas (QSS + JSON)](#temas-qss--json)
- [Estrutura e Exemplo](#estrutura-e-exemplo)
- [ThemeService](#themeservice)
- [Editor e Painel](#editor-e-painel)
- [QSS Placeholders](#qss-placeholders-qss_renderer)
- [Toasts e Notificações](#toasts-e-notificações)
- [APIs](#apis)
- [Centro de Notificações](#centro-de-notificações-push-à-direita)
- [Exemplo](#exemplo)
- [CLI](#cli)
- [new page](#new-page)
- [new subpage](#new-subpage)
- [examples](#examples)
- [manifest-update](#manifest-update)
- [clean-pages](#clean-pages)
- [Guia completo](#cli-detalhado-guia-completo)
- [Criação Manual de Páginas](#criando-páginas-manualmente-sem-cli)
- [Criação Manual de Subpáginas](#subpáginas-manualmente-sem-cli)
- [Widgets: Guia Prático](#widgets-guia-prático-cookbook)
- [Buttons](#buttons-controlssbutton--primarybutton)
- [Command Buttons](#botão-de-comando-taskrunner)
- [Confirmação + Comando](#confirmação--comando)
- [Navegação por Rota](#navegar-por-rota-route_button)
- [Controles Básicos](#toggle--checkbox--textinput--select)
- [Ícone e Link](#ícone--linklabel)
- [Popover de Ajuda](#popover-de-ajuda-hover)
- [ExpandirPainel](#expandircolapsar-painel-expandmore)
- [Slider Progresso](#slider-em-modo-progresso-não-interativo)
- [Scroll Customizado](#scroll-customizado)
- [AsyncTaskButton](#asynctaskbutton-tarefas-assíncronas)
- [Toasts](#toasts-simples-ação-e-progresso)
- [LoadingOverlay](#loadingoverlay-manual)
- [Quick Open](#quick-open-programático)
- [Personalização Visual](#personalização-visual--chrome)
- [Temas: Dicas Avançadas](#temas-dicas--avançado)
- [Centro de Notificações (Avançado)](#centro-de-notificações-avançado)
- [Troubleshooting](#troubleshooting)
- [Boas Práticas](#boas-práticas)
- [Roadmap](#roadmap)
- [Blueprint de Página (modelo recomendado)](#blueprint-de-página-modelo-recomendado)
- [Qualidade de Código (Linting & Style)](#qualidade-de-código-linting--style)
---
## Instalacao (PyPI) e uso rapido
Instale a biblioteca (ou atualize):
```
pip install ui-fram-skk
# ou
pip install -U ui-fram-skk
```
Comece um projeto novo e rode:
```
# 1) Inicialize o esqueleto de app (cria app/, assets/, pages/, etc.)
ui-cli init
# 2) Rode o app gerado
python -m app.app
# 3) (Opcional) Gere paginas com o CLI
ui-cli new page "Relatorios" --route relatorios --label "Relatorios" --sidebar --order 10
```
Ajuda rapida do CLI:
```
ui-cli --help
```
---
## Visão Geral
Principais recursos
- Janela frameless com sombra e cantos arredondados.
- Roteamento de páginas com auto‑descoberta e manifesto.
- Temas dinâmicos via QSS + JSON, com animação suave entre temas.
- Quick Open (Ctrl+K) para navegar por rotas rapidamente.
- TopBar com breadcrumb clicável e sino com badge de não lidas.
- Centro de Notificações (push à direita) integrado aos toasts.
- Toasts simples, com ações e com progresso.
- Persistência de tema e última rota em cache JSON.
- CLI para scaffolding, exemplos, limpeza e manifesto.
- Injeção de dependências (task_runner, theme_service) em páginas.
Novidades
- Router v2: rotas hierárquicas (`home/ferramentas/detalhes`), histórico (Alt+Left/Alt+Right), `routeChanged` e hook `on_route(params)` por página.
- Quick Open (Ctrl+K) via `ui/dialogs/quick_open.py`.
- Centro de Notificações com PushSidePanel e `notification_bus()`.
- ThemeService: watcher de `base.qss` e temas, tokens derivados e dump do QSS aplicado.
- TitleBar com ícone animado (cross‑fade) e sincronização com o tema.
- TaskRunnerAdapter aceita `async def run_task(...)`.
---
## Arquitetura
Mapa de diretórios
```
app/
app.py
settings.py
assets/
icons/ (app/, client/)
qss/
themes/
cache/
pages/
base_page.py
home_page.py
notificacoes.py
registry.py
settings.py
subpages/
guia_rapido_page.py
theme_editor.py
ui/
core/
app.py
app_controller.py
command_bus.py
frameless_window.py
interface_ports.py
main.py
router.py
settings.py
theme_service.py
toast_manager.py
utils/ (factories.py, guard.py, paths.py)
dialogs/ (quick_open.py)
services/ (qss_renderer.py, task_runner_adapter.py, theme_repository_json.py)
splash/ (splash.py)
widgets/ (async_button.py, buttons.py, loading_overlay.py,
overlay_sidebar.py, push_sidebar.py, settings_sidebar.py,
titlebar.py, toast.py, topbar.py)
requirements.txt
license
README.md
```
---
## Execução e Atalhos
Executar
```
python -m ui.core.main
# Alternativas
python -m app.app
python ui/core/main.py
```
Configurações (app/settings.py)
```
APP_TITLE = "Meu App"
DEFAULT_THEME = "Aku"
FIRST_PAGE = "home"
```
Atalhos de teclado
- Alt+Left / Alt+Right: histórico do Router (voltar/avançar).
- Ctrl+K: Quick Open (busca/abre rotas).
- Ctrl+M: minimizar com animação.
- Ctrl+Enter: maximizar/restaurar.
---
## Páginas e Roteamento
Manifesto (app/assets/pages_manifest.json)
```
[
{ "route": "home", "label": "Início", "sidebar": true, "order": 0,
"factory": "app.pages.home_page:build" }
]
```
Auto‑descoberta (app/pages)
Qualquer módulo com `PAGE` e `build(...)` é detectado. Exemplo:
```
PAGE = { "route": "relatorios", "label": "Relatórios", "sidebar": True, "order": 10 }
def build(task_runner=None, theme_service=None) -> QWidget:
...
```
Injeção de dependências
- `task_runner`: executa tarefas de negócio (I/O, rede, etc.).
- `theme_service`: reage a trocas de tema e fornece tokens.
Recursos do Router
- `router.go(path, params={})` navega para uma rota (com `/`).
- Sinal `routeChanged(path, params)` para persistência/breadcrumb/log.
- Histórico: `go_back()` / `go_forward()` (Alt+Left/Alt+Right).
- Hook por página: `on_route(self, params: dict)`.
---
## Temas (QSS + JSON)
Estrutura
- QSS base: `app/assets/qss/base.qss`
- JSON de variáveis: `app/assets/themes/*.json`
Exemplo
```
{
"vars": { "bg": "#1a1a1a", "text": "#ffffff", "accent": "#4285f4" }
}
```
ThemeService
- Anima troca de tema (com ou sem animação).
- Observa `base.qss` e a pasta de temas (hot‑reload).
- Emite `themeApplied`, `themesChanged`, `themeTokensChanged`.
- Persiste tema em `app/assets/cache/_ui_exec_settings.json`.
Editor e painel
- Painel (engrenagem na TitleBar) para selecionar/criar/editar/excluir temas.
- Persistência via `JsonThemeRepository` (gravação atômica).
- Ícone do app é sincronizado com o tema ativo.
QSS placeholders (qss_renderer)
- `{{token}}`, `{token}`, `${token}` com defaults e derivados úteis (ex.: `content_bg`).
---
## Toasts e Notificações
APIs
- `show_toast(parent, text, kind="info", timeout_ms=2400, persist=False)`
- `show_action_toast(parent, title, text, kind="info", actions=[...], sticky=False, timeout_ms=3200, persist=False)`
- `ProgressToast.start(parent, text, kind="info", cancellable=True)` (com `update(...)`/`finish(...)`)
- `notification_bus()` para integração com o Centro.
Centro de Notificações (push à direita)
- Abre/fecha pela TopBar (sino) e exibe entradas persistidas.
- Toasts dispensados podem virar entradas do Centro (título/texto/flags/ações).
- Largura redimensionável por “grip”, badge de não lidas na TopBar.
Exemplo
```
from ui.widgets.toast import (
show_toast, show_action_toast, ProgressToast, notification_bus
)
show_toast(parent, text="Ação concluída!", kind="info")
show_action_toast(
parent,
title="Exportação",
text="Arquivo gerado com sucesso.",
kind="ok",
actions=[{"label": "Abrir pasta", "command": "abrir_pasta", "payload": {"path": "..."}}],
persist=True,
)
pt = ProgressToast.start(parent, "Carregando dados...", kind="info", cancellable=True)
pt.update(5, 10)
pt.finish(True, "Processo finalizado!")
```
---
## CLI
Ferramentas para gerar páginas, criar exemplos, atualizar o manifesto e limpar o projeto. Operam em `app/pages` e mantêm `app/assets/pages_manifest.json` alinhado.
### new page
- O que faz: cria uma página padrão com `PAGE`, `build()`, hook `on_route()` e estrutura com `QScrollArea` pronta para conteúdos longos.
- Uso rápido (bash):
```bash
# 1) Cria página "Relatórios" com rota e label configurados
python -m ui.cli new page "Relatorios" \
--route relatorios \
--label "Relatórios" \
--order 10 \
--sidebar
# 2) (Opcional) Sobrescrever arquivo se já existir
python -m ui.cli new page "Relatorios" --route relatorios --force
```
- Resultado: arquivo `app/pages/relatorios_page.py` criado e entrada adicionada/atualizada no manifesto.
### new subpage
- O que faz: cria uma subpágina (rota `pai/filho`, ex.: `home/detalhes`). Útil para seções internas.
- Uso (bash):
```bash
# Cria uma subpágina "Detalhes" sob a rota pai "home"
python -m ui.cli new subpage "Detalhes" \
--parent home \
--route detalhes \
--label "Detalhes" \
--order 11 \
--sidebar
```
- Resultado: arquivo `app/pages/home_detalhes_page.py` (nome normalizado) e manifesto atualizado.
### examples
- O que faz: gera `examples_widgets_page.py` com usos práticos (botões, AsyncTaskButton, toasts) para consulta.
- Uso (bash):
```bash
python -m ui.cli examples
```
- Resultado: rota `examples` adicionada ao manifesto para navegação rápida.
### manifest-update
- O que faz: reescreve `app/assets/pages_manifest.json` com base na auto‑descoberta de `app.pages.*` (lendo `PAGE` e `build()`).
- Uso (bash):
```bash
python -m ui.cli manifest-update
```
- Dica: use após mover/renomear módulos manualmente para sincronizar o manifesto.
### clean-pages
- O que faz: remove páginas de exemplo e recria uma Home mínima (rota `home`).
- Uso (bash):
```bash
# Limpeza e criação da Home mínima
python -m ui.cli clean-pages
# (Opcional) Reconstruir manifesto via auto‑descoberta logo após a limpeza
python -m ui.cli clean-pages --rebuild-manifest
```
- Resultado: projeto enxuto para começar; somente a Home permanece.
---
## Widgets Inclusos
- TitleBar: barra de título com ícone animado, botões min/max/close e engrenagem.
- TopBar: breadcrumb clicável, sino com badge e menu.
- OverlaySidePanel: menu lateral (overlay) com scrim.
- SettingsSidePanel: painel de configurações (direita).
- PushSidePanel: painel lateral (Centro de Notificações), redimensionável.
- LoadingOverlay: overlay de carregamento sensível ao tema (GIF).
- Toast / ActionToast / ProgressToast: notificações flutuantes integradas ao Centro.
- AsyncButton: execução assíncrona com feedback visual.
- QuickOpenDialog: busca/abre rotas (Ctrl+K).
---
## Tutoriais Rápidos
Criar página
```
python -m ui.cli new page "Financeiro" --route financeiro --sidebar
```
Gerada com `PAGE`, `build()` e `QScrollArea`; `build(task_runner=None, theme_service=None)` recebe dependências injetadas.
Criar tema
1. Duplique `app/assets/themes/Dark.json`.
2. Renomeie (ex.: `MyTheme.json`) e ajuste cores.
3. Selecione pelo painel de configurações (engrenagem na TitleBar).
Integrar TaskRunner
```
from ui.services.task_runner_adapter import TaskRunnerAdapter
class MyRunner:
def run_task(self, name, payload):
if name == "calcular_relatorio":
return {"ok": True, "data": "Relatório gerado"}
return {"ok": False, "error": "Ação inválida"}
controller = AppController(task_runner=TaskRunnerAdapter(MyRunner()))
```
---
## Boas Práticas
- Centralize estilos no `base.qss`; evite `setStyleSheet` direto.
- Separe UI de lógica de negócio (via `task_runner`).
- Padronize rotas (kebab‑case) e labels claros.
- Use `theme_service` para cores/tokens e reatividade visual.
- Mantenha a estrutura de pastas limpa para facilitar autoload.
---
## Roadmap
- Subpáginas e histórico de navegação aprimorados.
- Centro de notificações persistente/filtrável.
- Logger de eventos integrável.
- Pesquisa global e atalhos adicionais.
- Gerenciador de estado leve (Qt Signals).
- Empacotamento (PyInstaller/Briefcase).
---
## Instalação e Setup
- Requisitos
- Python 3.10+
- Pip recente e ambiente virtual recomendado
- Windows/macOS/Linux com Qt (PySide6)
- Passo a passo
- Crie e ative um ambiente virtual
- Windows: `python -m venv .venv && .venv\Scripts\activate`
- macOS/Linux: `python -m venv .venv && source .venv/bin/activate`
- Instale dependências: `pip install -r requirements.txt`
- Rode o app: `python -m ui.core.main`
---
## CLI Detalhado (guia completo)
O CLI acelera tarefas comuns. Abaixo, as opções e impactos de cada comando.
- new page
- Cria um arquivo em `app/pages/<rota>_page.py` com:
- `PAGE = { route, label, sidebar, order }`
- `build(task_runner=None, theme_service=None)`
- Estrutura com `QScrollArea` pronta para conteúdo extenso
- Exemplo:
- `python -m ui.cli new page "Relatorios" --route relatorios --label Relatórios --order 10 --sidebar`
- Manifesto: faz “upsert” de uma entrada coerente em `app/assets/pages_manifest.json`.
- new subpage
- Gera uma subpágina com rota `pai/filho` (ex.: `home/detalhes`).
- Exemplo:
- `python -m ui.cli new subpage "Detalhes" --parent home --route detalhes --label Detalhes --order 11 --sidebar`
- Arquivo: `app/pages/home_detalhes_page.py` (rota normalizada).
- Manifesto: atualiza com a nova rota.
- examples
- Cria `examples_widgets_page.py` com usos práticos de botões, AsyncTaskButton e toasts.
- Exemplo: `python -m ui.cli examples`
- Manifesto: adiciona rota `examples`.
- manifest-update
- Reescreve completamente `app/assets/pages_manifest.json` com base na auto‑descoberta de `app.pages.*`.
- Útil após mover/renomear páginas manualmente.
- Exemplo: `python -m ui.cli manifest-update`
- clean-pages
- Remove páginas pré‑programadas de exemplo e cria uma Home mínima.
- Exemplo: `python -m ui.cli clean-pages` (ou `--rebuild-manifest` para reconstruir o manifesto via descoberta)
Observações importantes
- O nome do arquivo gerado é a rota com barras substituídas por sublinhados + `_page.py`.
- A descoberta padrão importa `app.pages.*`; mantenha páginas dentro de `app/pages`.
---
## Criando Páginas Manualmente (sem CLI)
1) Crie um arquivo em `app/pages/minha_pagina_page.py` com:
```
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea, QFrame
PAGE = { "route": "minha-pagina", "label": "Minha Página", "sidebar": True, "order": 50 }
class MinhaPaginaPage(QWidget):
def __init__(self, task_runner=None, theme_service=None):
super().__init__()
root = QVBoxLayout(self); root.setContentsMargins(14,14,14,14); root.setSpacing(12)
scroll = QScrollArea(self); scroll.setWidgetResizable(True)
wrap = QFrame(); wrap_l = QVBoxLayout(wrap); wrap_l.setContentsMargins(0,0,0,0); wrap_l.setSpacing(10)
wrap_l.addWidget(QLabel("Conteúdo da Minha Página")); wrap_l.addStretch(1)
scroll.setWidget(wrap); root.addWidget(scroll, 1)
def on_route(self, params: dict | None = None):
# Atualize conteúdo com base em params quando a rota for ativada
pass
def build(task_runner=None, theme_service=None) -> QWidget:
return MinhaPaginaPage(task_runner=task_runner, theme_service=theme_service)
```
2) Registre no manifesto (opcional caso confie na auto‑descoberta)
```
// app/assets/pages_manifest.json
[
{ "route": "home", "label": "Início", "sidebar": true, "order": 0,
"factory": "app.pages.home_page:build" },
{ "route": "minha-pagina", "label": "Minha Página", "sidebar": true, "order": 50,
"factory": "app.pages.minha_pagina_page:build" }
]
```
3) Execute e navegue: a página aparecerá na sidebar e breadcrumbs.
---
## Subpáginas Manualmente (sem CLI)
1) Rota e arquivo
- Para uma subpágina de `home`, crie `app/pages/home_detalhes_page.py` com `PAGE = { "route": "home/detalhes", ... }`.
- O breadcrumb mostrará `Home / Detalhes` automaticamente.
2) SideBar e order
- `sidebar: true` para aparecer no menu; `false` para rotas internas.
- Ajuste `order` para ordenar a exibição na sidebar.
3) Navegação programática
```
def _go(self, path: str):
win = self.window(); r = getattr(win, "router", None)
if r: r.go(path)
```
---
## Widgets: Guia Prático (Cookbook)
Botões (Controls.Button / PrimaryButton)
```
from ui.widgets.buttons import Controls
btn = Controls.Button("Primário", size_preset="md")
btn.setProperty("variant", "primary") # outras: "chip", "ghost" (via QSS)
btn.clicked.connect(lambda: print("clicado"))
```
Tamanhos e variantes
- `size_preset`: `sm`, `md`, `lg`, `xl`, `char` (um caractere)
- `variant` (QSS): `primary`, `chip`, `ghost` (e outras que seu QSS definir)
Botão de comando (TaskRunner)
```
from ui.widgets.buttons import command_button
btn = command_button(
text="Sincronizar",
command_name="sync",
task_runner=my_runner,
payload={"force": True},
disable_while_running=True,
lock_after_click=False,
size_preset="md"
)
```
Confirmação + comando
```
from ui.widgets.buttons import confirm_command_button
btn = confirm_command_button(
text="Excluir",
confirm_msg="Tem certeza?",
command_name="delete_item",
task_runner=my_runner,
payload={"id": 123},
size_preset="sm"
)
```
Navegar por rota (route_button)
```
from ui.widgets.buttons import route_button
go = lambda: self.window().router.go("home/detalhes")
btn = route_button("Ir", go, size_preset="sm")
```
Toggle / CheckBox / TextInput / Select
```
t = Controls.Toggle(); t.setChecked(True)
ck = Controls.CheckBox("Aceito termos")
inp = Controls.TextInput("Digite aqui...")
sel = Controls.InputList(); sel.addItems(["A", "B", "C"])
```
Ícone / LinkLabel
```
icon = Controls.IconButton("⋯", tooltip="Mais")
link = Controls.LinkLabel("Abrir documentação"); link.clicked.connect(lambda: print("abrir"))
```
Popover de ajuda (hover)
```
from ui.widgets.buttons import attach_popover
attach_popover(icon, "Ação X", "Executa a ação X", "Ctrl+X")
```
Expandir/colapsar painel (ExpandMore)
```
from ui.widgets.buttons import Controls
panel = QFrame(); panel.setLayout(QVBoxLayout()); panel.layout().addWidget(QLabel("Detalhes"))
exp = Controls.ExpandMore(panel, text_collapsed="Ver mais", text_expanded="Ver menos")
layout.addWidget(exp)
```
Slider em modo progresso (não interativo)
```
s = Controls.Slider(); s.setRange(0,100); s.setValue(42); s.setMode("progress")
```
Scroll customizado
```
scroll = Controls.ScrollArea(); scroll.setWidget(wrap)
```
AsyncTaskButton (tarefas assíncronas)
```
from ui.widgets.async_button import AsyncTaskButton
ab = AsyncTaskButton(
"Executar",
task_runner=my_runner,
command_name="processar",
payload={"limit": 10},
block_input=True, # opcional: bloqueia com overlay
use_overlay=True,
overlay_message="Processando...",
progress_text="Processando…", progress_kind="info", progress_cancellable=False,
)
ab.succeeded.connect(lambda *_: print("ok"))
ab.failed.connect(lambda *_: print("falha"))
layout.addWidget(ab)
```
Toasts (simples, ação e progresso)
```
from ui.widgets.toast import show_toast, show_action_toast, ProgressToast
show_toast(self, "Salvo com sucesso", kind="ok")
show_action_toast(
self, "Exportação", "Arquivo pronto.", kind="ok",
actions=[{"label":"Abrir pasta","command":"abrir_pasta","payload":{}}], persist=True
)
pt = ProgressToast.start(self, "Sincronizando…", kind="info", cancellable=True)
pt.set_indeterminate(True)
pt.update(3, 10)
pt.finish(True, "Concluído")
```
LoadingOverlay manual
```
from ui.widgets.loading_overlay import LoadingOverlay
ov = LoadingOverlay(self, message="Carregando…", block_input=True)
ov.show(); ...; ov.hide()
```
Quick Open programático
```
from ui.dialogs.quick_open import QuickOpenDialog
dlg = QuickOpenDialog(self.window().router._pages.keys(), parent=self)
dlg.exec()
```
---
## Personalização Visual & Chrome
- TitleBar: o ícone do app troca conforme o tema; ícones em `app/assets/icons/app/`.
- TopBar: breadcrumb e sino já integrados; badge reflete o Centro de Notificações.
- Sidebars: `OverlaySidePanel` (navegação) e `SettingsSidePanel` (configurações) estilizáveis via QSS.
- PushSidePanel (direita): painel do Centro de Notificações com redimensionamento por “grip”.
---
## Centro de Notificações (avançado)
Interaja com o barramento para criar/atualizar/remover entradas:
```
from ui.widgets.toast import notification_bus
bus = notification_bus()
bus.addEntry.emit({
"id": "abc123", "type": "info", "title": "Processo iniciado",
"text": "Aguarde…", "persist": True, "expires_on_finish": True
})
bus.updateEntry.emit({"id": "abc123", "text": "50%"})
bus.finishEntry.emit("abc123")
bus.removeEntry.emit("abc123")
```
---
## Temas: Dicas & Avançado
- Adicione variáveis em `*.json` na chave `vars` e faça referência no `base.qss` usando `{{token}}`, `{token}` ou `${token}`.
- Tokens derivados (ex.: `content_bg`) são gerados automaticamente.
- O ThemeService observa `base.qss` e temas; alterações são aplicadas em tempo real.
- Use o Editor de Temas (painel) para editar e salvar de forma segura (gravação atômica).
Exemplo de uso de token no QSS
```
QPushButton[variant="chip"] {
background: {{surface}}; color: {{text}}; border: 1px solid {{box_border}};
}
```
---
## Troubleshooting
- Página não aparece
- Verifique se o módulo está em `app/pages` e exporta `PAGE` e `build()`.
- Cheque `pages_manifest.json` ou rode `python -m ui.cli manifest-update`.
- QSS não aplica
- Verifique `app/assets/qss/base.qss`. O ThemeService emite sinais nas trocas; acompanhe logs.
- Toasts não aparecem
- Confirme se o app está rodando com `AppShell` padrão; toasts dependem do shell frameless.
---
## Blueprint de Página (modelo recomendado)
Use este esqueleto como base para novas páginas, mantendo consistência visual e técnica.
```python
from __future__ import annotations
from PySide6.QtWidgets import QWidget, QVBoxLayout, QScrollArea, QFrame, QLabel
# Metadados de rota (sidebar, ordem e rótulo)
PAGE = {
"route": "minha-pagina",
"label": "Minha Página",
"sidebar": True,
"order": 50,
}
class MinhaPaginaPage(QWidget):
def __init__(self, task_runner=None, theme_service=None):
super().__init__()
root = QVBoxLayout(self)
root.setContentsMargins(14, 14, 14, 14)
root.setSpacing(12)
# Área rolável para conteúdos longos
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
# Container real do conteúdo
wrap = QFrame()
wrap_l = QVBoxLayout(wrap)
wrap_l.setContentsMargins(0, 0, 0, 0)
wrap_l.setSpacing(10)
# Conteúdo inicial
wrap_l.addWidget(QLabel("Olá, mundo!"))
wrap_l.addStretch(1)
scroll.setWidget(wrap)
root.addWidget(scroll, 1)
# Hook de ciclo de vida: chamado quando a rota é ativada
def on_route(self, params: dict | None = None):
# Atualize conteúdo/estado a partir de params, se necessário
pass
def build(task_runner=None, theme_service=None) -> QWidget:
return MinhaPaginaPage(task_runner=task_runner, theme_service=theme_service)
```
Boas práticas para páginas
- Use `QScrollArea` como container padrão para manter UX consistente.
- Centralize estilos no `base.qss`; evite `setStyleSheet` diretamente em widgets.
- Utilize `on_route(params)` para reagir a navegação em vez de lógicas no `__init__`.
- Prefira nomes de rota em kebab-case (ex.: `relatorios-anuais`).
---
## Qualidade de Código (Linting & Style)
Ferramentas sugeridas
- Ruff (lint & fix): `pip install ruff` • `ruff check .` • `ruff check . --fix`
- Black (format): `pip install black` • `black .`
- isort (imports): `pip install isort` • `isort .`
- mypy (tipagem opcional): `pip install mypy` • `mypy ui app`
Dicas de estilo
- Tipagem: anote funções e atributos públicos; use `Optional[...]` e `| None` onde apropriado.
- Nomes: rotas em kebab-case; módulos/arquivos em snake_case; classes em PascalCase; variáveis/funções em snake_case.
- Sinais/slots: prefira nomes descritivos (`openNotificationsRequested`, `setUnreadCountRequested`).
- Páginas: exponha `PAGE` + `build(...)`; use `on_route(params)` para ciclo de vida.
- QSS: declare variantes com `setProperty("variant", ...)` e tamanhos com `setProperty("size", ...)`.
- Imports: agrupe padrão→terceiros→locais; mantenha ordem alfabética (use `isort`).
- Formatação: 88 colunas (black), quebras legíveis, evite linhas muito longas.
Sobre o arquivo pyproject.toml
- O que é: um arquivo único de configuração (na raiz do projeto) que centraliza as regras de ferramentas como Black, Ruff, isort e mypy.
- Onde fica: `pyproject.toml` na raiz deste repositório.
- Por que usar: padroniza estilo e qualidade para todo o time, reduzindo ruído em PRs e tornando os comandos previsíveis.
- Como funciona: cada ferramenta lê suas opções diretamente do `pyproject.toml`, então você não precisa passar flags nos comandos.
Comandos do dia a dia (usam o pyproject.toml):
```
# Lint (apenas checar)
ruff check .
# Lint com correções automáticas
ruff check . --fix
# Formatar código (opiniado)
black .
# Organizar imports
isort .
# Checar tipos (opcional, gradual)
mypy ui app
```
Sugestões de fluxo
- Para iniciantes: rode `ruff check . --fix` e depois `black .`; se houver import bagunçado, rode `isort .`.
- Para quem já domina: configure um pre-commit com `ruff`, `black` e `isort` ou adicione esses passos no seu CI.
Raw data
{
"_id": null,
"home_page": null,
"name": "ui-fram-skk",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "pyside6, qt, ui, desktop, framework",
"author": null,
"author_email": "Jo\u00e3o Vitor de Souza Siqueira <skkiler13ka@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/1e/62/16f0874c5c5f582d953da5f5cb3c087e2f202724d2d7563d5ec58e966460/ui_fram_skk-0.1.1.tar.gz",
"platform": null,
"description": "\ufeff<p align=\"center\">\r\n <img src=\"app/assets/icons/app/app.ico\" width=\"96\" alt=\"App Icon\" />\r\n</p>\r\n<p align=\"center\">\r\n <a href=\"README.en.md\">Read this in English</a>\r\n</p>\r\n\r\n---\r\n# UI Exec Framework (PySide6)\r\nBase moderna para aplicativos desktop com Python + PySide6. Inclui janela frameless, roteamento hier\u00e1rquico, sistema de temas (QSS + JSON) com anima\u00e7\u00e3o, toasts e Centro de Notifica\u00e7\u00f5es, al\u00e9m de um CLI para produtividade.\r\n---\r\n## Sum\u00e1rio\r\n- [Vis\u00e3o Geral](#vis\u00e3o-geral)\r\n- [Instalacao (PyPI) e uso rapido](#instalacao-pypi-e-uso-rapido)\r\n- [Arquitetura](#arquitetura)\r\n- [Instala\u00e7\u00e3o e Setup](#instala\u00e7\u00e3o-e-setup)\r\n- [Execu\u00e7\u00e3o e Atalhos](#execu\u00e7\u00e3o-e-atalhos)\r\n- [P\u00e1ginas e Roteamento](#p\u00e1ginas-e-roteamento)\r\n - [Manifesto](#manifesto-appassetspages_manifestjson)\r\n - [Auto\u2011descoberta](#auto\u2011descoberta-apppages)\r\n - [Inje\u00e7\u00e3o de depend\u00eancias](#inje\u00e7\u00e3o-de-depend\u00eancias)\r\n - [Recursos do Router](#recursos-do-router)\r\n- [Temas (QSS + JSON)](#temas-qss--json)\r\n - [Estrutura e Exemplo](#estrutura-e-exemplo)\r\n - [ThemeService](#themeservice)\r\n - [Editor e Painel](#editor-e-painel)\r\n - [QSS Placeholders](#qss-placeholders-qss_renderer)\r\n- [Toasts e Notifica\u00e7\u00f5es](#toasts-e-notifica\u00e7\u00f5es)\r\n - [APIs](#apis)\r\n - [Centro de Notifica\u00e7\u00f5es](#centro-de-notifica\u00e7\u00f5es-push-\u00e0-direita)\r\n - [Exemplo](#exemplo)\r\n- [CLI](#cli)\r\n - [new page](#new-page)\r\n - [new subpage](#new-subpage)\r\n - [examples](#examples)\r\n - [manifest-update](#manifest-update)\r\n - [clean-pages](#clean-pages)\r\n - [Guia completo](#cli-detalhado-guia-completo)\r\n- [Cria\u00e7\u00e3o Manual de P\u00e1ginas](#criando-p\u00e1ginas-manualmente-sem-cli)\r\n- [Cria\u00e7\u00e3o Manual de Subp\u00e1ginas](#subp\u00e1ginas-manualmente-sem-cli)\r\n- [Widgets: Guia Pr\u00e1tico](#widgets-guia-pr\u00e1tico-cookbook)\r\n - [Buttons](#buttons-controlssbutton--primarybutton)\r\n - [Command Buttons](#bot\u00e3o-de-comando-taskrunner)\r\n - [Confirma\u00e7\u00e3o + Comando](#confirma\u00e7\u00e3o--comando)\r\n - [Navega\u00e7\u00e3o por Rota](#navegar-por-rota-route_button)\r\n - [Controles B\u00e1sicos](#toggle--checkbox--textinput--select)\r\n - [\u00cdcone e Link](#\u00edcone--linklabel)\r\n - [Popover de Ajuda](#popover-de-ajuda-hover)\r\n - [ExpandirPainel](#expandircolapsar-painel-expandmore)\r\n - [Slider Progresso](#slider-em-modo-progresso-n\u00e3o-interativo)\r\n - [Scroll Customizado](#scroll-customizado)\r\n - [AsyncTaskButton](#asynctaskbutton-tarefas-ass\u00edncronas)\r\n - [Toasts](#toasts-simples-a\u00e7\u00e3o-e-progresso)\r\n - [LoadingOverlay](#loadingoverlay-manual)\r\n - [Quick Open](#quick-open-program\u00e1tico)\r\n- [Personaliza\u00e7\u00e3o Visual](#personaliza\u00e7\u00e3o-visual--chrome)\r\n - [Temas: Dicas Avan\u00e7adas](#temas-dicas--avan\u00e7ado)\r\n- [Centro de Notifica\u00e7\u00f5es (Avan\u00e7ado)](#centro-de-notifica\u00e7\u00f5es-avan\u00e7ado)\r\n- [Troubleshooting](#troubleshooting)\r\n- [Boas Pr\u00e1ticas](#boas-pr\u00e1ticas)\r\n- [Roadmap](#roadmap)\r\n- [Blueprint de P\u00e1gina (modelo recomendado)](#blueprint-de-p\u00e1gina-modelo-recomendado)\r\n- [Qualidade de C\u00f3digo (Linting & Style)](#qualidade-de-c\u00f3digo-linting--style)\r\n---\r\n\r\n\r\n## Instalacao (PyPI) e uso rapido\r\n\r\nInstale a biblioteca (ou atualize):\r\n\r\n```\r\npip install ui-fram-skk\r\n# ou\r\npip install -U ui-fram-skk\r\n```\r\n\r\nComece um projeto novo e rode:\r\n\r\n```\r\n# 1) Inicialize o esqueleto de app (cria app/, assets/, pages/, etc.)\r\nui-cli init\r\n\r\n# 2) Rode o app gerado\r\npython -m app.app\r\n\r\n# 3) (Opcional) Gere paginas com o CLI\r\nui-cli new page \"Relatorios\" --route relatorios --label \"Relatorios\" --sidebar --order 10\r\n```\r\n\r\nAjuda rapida do CLI:\r\n\r\n```\r\nui-cli --help\r\n```\r\n\r\n---\r\n\r\n## Vis\u00e3o Geral\r\n\r\nPrincipais recursos\r\n- Janela frameless com sombra e cantos arredondados.\r\n- Roteamento de p\u00e1ginas com auto\u2011descoberta e manifesto.\r\n- Temas din\u00e2micos via QSS + JSON, com anima\u00e7\u00e3o suave entre temas.\r\n- Quick Open (Ctrl+K) para navegar por rotas rapidamente.\r\n- TopBar com breadcrumb clic\u00e1vel e sino com badge de n\u00e3o lidas.\r\n- Centro de Notifica\u00e7\u00f5es (push \u00e0 direita) integrado aos toasts.\r\n- Toasts simples, com a\u00e7\u00f5es e com progresso.\r\n- Persist\u00eancia de tema e \u00faltima rota em cache JSON.\r\n- CLI para scaffolding, exemplos, limpeza e manifesto.\r\n- Inje\u00e7\u00e3o de depend\u00eancias (task_runner, theme_service) em p\u00e1ginas.\r\nNovidades\r\n- Router v2: rotas hier\u00e1rquicas (`home/ferramentas/detalhes`), hist\u00f3rico (Alt+Left/Alt+Right), `routeChanged` e hook `on_route(params)` por p\u00e1gina.\r\n- Quick Open (Ctrl+K) via `ui/dialogs/quick_open.py`.\r\n- Centro de Notifica\u00e7\u00f5es com PushSidePanel e `notification_bus()`.\r\n- ThemeService: watcher de `base.qss` e temas, tokens derivados e dump do QSS aplicado.\r\n- TitleBar com \u00edcone animado (cross\u2011fade) e sincroniza\u00e7\u00e3o com o tema.\r\n- TaskRunnerAdapter aceita `async def run_task(...)`.\r\n\r\n---\r\n\r\n## Arquitetura\r\n\r\nMapa de diret\u00f3rios\r\n```\r\napp/\r\n app.py\r\n settings.py\r\n assets/\r\n icons/ (app/, client/)\r\n qss/\r\n themes/\r\n cache/\r\n pages/\r\n base_page.py\r\n home_page.py\r\n notificacoes.py\r\n registry.py\r\n settings.py\r\n subpages/\r\n guia_rapido_page.py\r\n theme_editor.py\r\nui/\r\n core/\r\n app.py\r\n app_controller.py\r\n command_bus.py\r\n frameless_window.py\r\n interface_ports.py\r\n main.py\r\n router.py\r\n settings.py\r\n theme_service.py\r\n toast_manager.py\r\n utils/ (factories.py, guard.py, paths.py)\r\n dialogs/ (quick_open.py)\r\n services/ (qss_renderer.py, task_runner_adapter.py, theme_repository_json.py)\r\n splash/ (splash.py)\r\n widgets/ (async_button.py, buttons.py, loading_overlay.py,\r\n overlay_sidebar.py, push_sidebar.py, settings_sidebar.py,\r\n titlebar.py, toast.py, topbar.py)\r\nrequirements.txt\r\nlicense\r\nREADME.md\r\n```\r\n\r\n---\r\n\r\n## Execu\u00e7\u00e3o e Atalhos\r\n\r\nExecutar\r\n```\r\npython -m ui.core.main\r\n# Alternativas\r\npython -m app.app\r\npython ui/core/main.py\r\n```\r\nConfigura\u00e7\u00f5es (app/settings.py)\r\n```\r\nAPP_TITLE = \"Meu App\"\r\nDEFAULT_THEME = \"Aku\"\r\nFIRST_PAGE = \"home\"\r\n```\r\nAtalhos de teclado\r\n- Alt+Left / Alt+Right: hist\u00f3rico do Router (voltar/avan\u00e7ar).\r\n- Ctrl+K: Quick Open (busca/abre rotas).\r\n- Ctrl+M: minimizar com anima\u00e7\u00e3o.\r\n- Ctrl+Enter: maximizar/restaurar.\r\n\r\n---\r\n\r\n## P\u00e1ginas e Roteamento\r\n\r\nManifesto (app/assets/pages_manifest.json)\r\n```\r\n[\r\n { \"route\": \"home\", \"label\": \"In\u00edcio\", \"sidebar\": true, \"order\": 0,\r\n \"factory\": \"app.pages.home_page:build\" }\r\n]\r\n```\r\nAuto\u2011descoberta (app/pages)\r\nQualquer m\u00f3dulo com `PAGE` e `build(...)` \u00e9 detectado. Exemplo:\r\n```\r\nPAGE = { \"route\": \"relatorios\", \"label\": \"Relat\u00f3rios\", \"sidebar\": True, \"order\": 10 }\r\ndef build(task_runner=None, theme_service=None) -> QWidget:\r\n ...\r\n```\r\nInje\u00e7\u00e3o de depend\u00eancias\r\n- `task_runner`: executa tarefas de neg\u00f3cio (I/O, rede, etc.).\r\n- `theme_service`: reage a trocas de tema e fornece tokens.\r\nRecursos do Router\r\n- `router.go(path, params={})` navega para uma rota (com `/`).\r\n- Sinal `routeChanged(path, params)` para persist\u00eancia/breadcrumb/log.\r\n- Hist\u00f3rico: `go_back()` / `go_forward()` (Alt+Left/Alt+Right).\r\n- Hook por p\u00e1gina: `on_route(self, params: dict)`.\r\n---\r\n## Temas (QSS + JSON)\r\nEstrutura\r\n- QSS base: `app/assets/qss/base.qss`\r\n- JSON de vari\u00e1veis: `app/assets/themes/*.json`\r\nExemplo\r\n```\r\n{\r\n \"vars\": { \"bg\": \"#1a1a1a\", \"text\": \"#ffffff\", \"accent\": \"#4285f4\" }\r\n}\r\n```\r\nThemeService\r\n- Anima troca de tema (com ou sem anima\u00e7\u00e3o).\r\n- Observa `base.qss` e a pasta de temas (hot\u2011reload).\r\n- Emite `themeApplied`, `themesChanged`, `themeTokensChanged`.\r\n- Persiste tema em `app/assets/cache/_ui_exec_settings.json`.\r\nEditor e painel\r\n- Painel (engrenagem na TitleBar) para selecionar/criar/editar/excluir temas.\r\n- Persist\u00eancia via `JsonThemeRepository` (grava\u00e7\u00e3o at\u00f4mica).\r\n- \u00cdcone do app \u00e9 sincronizado com o tema ativo.\r\nQSS placeholders (qss_renderer)\r\n- `{{token}}`, `{token}`, `${token}` com defaults e derivados \u00fateis (ex.: `content_bg`).\r\n---\r\n## Toasts e Notifica\u00e7\u00f5es\r\nAPIs\r\n- `show_toast(parent, text, kind=\"info\", timeout_ms=2400, persist=False)`\r\n- `show_action_toast(parent, title, text, kind=\"info\", actions=[...], sticky=False, timeout_ms=3200, persist=False)`\r\n- `ProgressToast.start(parent, text, kind=\"info\", cancellable=True)` (com `update(...)`/`finish(...)`)\r\n- `notification_bus()` para integra\u00e7\u00e3o com o Centro.\r\nCentro de Notifica\u00e7\u00f5es (push \u00e0 direita)\r\n- Abre/fecha pela TopBar (sino) e exibe entradas persistidas.\r\n- Toasts dispensados podem virar entradas do Centro (t\u00edtulo/texto/flags/a\u00e7\u00f5es).\r\n- Largura redimension\u00e1vel por \u201cgrip\u201d, badge de n\u00e3o lidas na TopBar.\r\nExemplo\r\n```\r\nfrom ui.widgets.toast import (\r\n show_toast, show_action_toast, ProgressToast, notification_bus\r\n)\r\nshow_toast(parent, text=\"A\u00e7\u00e3o conclu\u00edda!\", kind=\"info\")\r\nshow_action_toast(\r\n parent,\r\n title=\"Exporta\u00e7\u00e3o\",\r\n text=\"Arquivo gerado com sucesso.\",\r\n kind=\"ok\",\r\n actions=[{\"label\": \"Abrir pasta\", \"command\": \"abrir_pasta\", \"payload\": {\"path\": \"...\"}}],\r\n persist=True,\r\n)\r\npt = ProgressToast.start(parent, \"Carregando dados...\", kind=\"info\", cancellable=True)\r\npt.update(5, 10)\r\npt.finish(True, \"Processo finalizado!\")\r\n```\r\n---\r\n## CLI\r\nFerramentas para gerar p\u00e1ginas, criar exemplos, atualizar o manifesto e limpar o projeto. Operam em `app/pages` e mant\u00eam `app/assets/pages_manifest.json` alinhado.\r\n### new page\r\n- O que faz: cria uma p\u00e1gina padr\u00e3o com `PAGE`, `build()`, hook `on_route()` e estrutura com `QScrollArea` pronta para conte\u00fados longos.\r\n- Uso r\u00e1pido (bash):\r\n```bash\r\n# 1) Cria p\u00e1gina \"Relat\u00f3rios\" com rota e label configurados\r\npython -m ui.cli new page \"Relatorios\" \\\r\n --route relatorios \\\r\n --label \"Relat\u00f3rios\" \\\r\n --order 10 \\\r\n --sidebar\r\n# 2) (Opcional) Sobrescrever arquivo se j\u00e1 existir\r\npython -m ui.cli new page \"Relatorios\" --route relatorios --force\r\n```\r\n- Resultado: arquivo `app/pages/relatorios_page.py` criado e entrada adicionada/atualizada no manifesto.\r\n### new subpage\r\n- O que faz: cria uma subp\u00e1gina (rota `pai/filho`, ex.: `home/detalhes`). \u00datil para se\u00e7\u00f5es internas.\r\n- Uso (bash):\r\n```bash\r\n# Cria uma subp\u00e1gina \"Detalhes\" sob a rota pai \"home\"\r\npython -m ui.cli new subpage \"Detalhes\" \\\r\n --parent home \\\r\n --route detalhes \\\r\n --label \"Detalhes\" \\\r\n --order 11 \\\r\n --sidebar\r\n```\r\n- Resultado: arquivo `app/pages/home_detalhes_page.py` (nome normalizado) e manifesto atualizado.\r\n### examples\r\n- O que faz: gera `examples_widgets_page.py` com usos pr\u00e1ticos (bot\u00f5es, AsyncTaskButton, toasts) para consulta.\r\n- Uso (bash):\r\n```bash\r\npython -m ui.cli examples\r\n```\r\n- Resultado: rota `examples` adicionada ao manifesto para navega\u00e7\u00e3o r\u00e1pida.\r\n### manifest-update\r\n- O que faz: reescreve `app/assets/pages_manifest.json` com base na auto\u2011descoberta de `app.pages.*` (lendo `PAGE` e `build()`).\r\n- Uso (bash):\r\n```bash\r\npython -m ui.cli manifest-update\r\n```\r\n- Dica: use ap\u00f3s mover/renomear m\u00f3dulos manualmente para sincronizar o manifesto.\r\n### clean-pages\r\n- O que faz: remove p\u00e1ginas de exemplo e recria uma Home m\u00ednima (rota `home`).\r\n- Uso (bash):\r\n```bash\r\n# Limpeza e cria\u00e7\u00e3o da Home m\u00ednima\r\npython -m ui.cli clean-pages\r\n# (Opcional) Reconstruir manifesto via auto\u2011descoberta logo ap\u00f3s a limpeza\r\npython -m ui.cli clean-pages --rebuild-manifest\r\n```\r\n- Resultado: projeto enxuto para come\u00e7ar; somente a Home permanece.\r\n---\r\n## Widgets Inclusos\r\n- TitleBar: barra de t\u00edtulo com \u00edcone animado, bot\u00f5es min/max/close e engrenagem.\r\n- TopBar: breadcrumb clic\u00e1vel, sino com badge e menu.\r\n- OverlaySidePanel: menu lateral (overlay) com scrim.\r\n- SettingsSidePanel: painel de configura\u00e7\u00f5es (direita).\r\n- PushSidePanel: painel lateral (Centro de Notifica\u00e7\u00f5es), redimension\u00e1vel.\r\n- LoadingOverlay: overlay de carregamento sens\u00edvel ao tema (GIF).\r\n- Toast / ActionToast / ProgressToast: notifica\u00e7\u00f5es flutuantes integradas ao Centro.\r\n- AsyncButton: execu\u00e7\u00e3o ass\u00edncrona com feedback visual.\r\n- QuickOpenDialog: busca/abre rotas (Ctrl+K).\r\n---\r\n## Tutoriais R\u00e1pidos\r\nCriar p\u00e1gina\r\n```\r\npython -m ui.cli new page \"Financeiro\" --route financeiro --sidebar\r\n```\r\nGerada com `PAGE`, `build()` e `QScrollArea`; `build(task_runner=None, theme_service=None)` recebe depend\u00eancias injetadas.\r\nCriar tema\r\n1. Duplique `app/assets/themes/Dark.json`.\r\n2. Renomeie (ex.: `MyTheme.json`) e ajuste cores.\r\n3. Selecione pelo painel de configura\u00e7\u00f5es (engrenagem na TitleBar).\r\nIntegrar TaskRunner\r\n```\r\nfrom ui.services.task_runner_adapter import TaskRunnerAdapter\r\nclass MyRunner:\r\n def run_task(self, name, payload):\r\n if name == \"calcular_relatorio\":\r\n return {\"ok\": True, \"data\": \"Relat\u00f3rio gerado\"}\r\n return {\"ok\": False, \"error\": \"A\u00e7\u00e3o inv\u00e1lida\"}\r\ncontroller = AppController(task_runner=TaskRunnerAdapter(MyRunner()))\r\n```\r\n---\r\n## Boas Pr\u00e1ticas\r\n- Centralize estilos no `base.qss`; evite `setStyleSheet` direto.\r\n- Separe UI de l\u00f3gica de neg\u00f3cio (via `task_runner`).\r\n- Padronize rotas (kebab\u2011case) e labels claros.\r\n- Use `theme_service` para cores/tokens e reatividade visual.\r\n- Mantenha a estrutura de pastas limpa para facilitar autoload.\r\n---\r\n## Roadmap\r\n- Subp\u00e1ginas e hist\u00f3rico de navega\u00e7\u00e3o aprimorados.\r\n- Centro de notifica\u00e7\u00f5es persistente/filtr\u00e1vel.\r\n- Logger de eventos integr\u00e1vel.\r\n- Pesquisa global e atalhos adicionais.\r\n- Gerenciador de estado leve (Qt Signals).\r\n- Empacotamento (PyInstaller/Briefcase).\r\n---\r\n## Instala\u00e7\u00e3o e Setup\r\n- Requisitos\r\n - Python 3.10+\r\n - Pip recente e ambiente virtual recomendado\r\n - Windows/macOS/Linux com Qt (PySide6)\r\n- Passo a passo\r\n - Crie e ative um ambiente virtual\r\n - Windows: `python -m venv .venv && .venv\\Scripts\\activate`\r\n - macOS/Linux: `python -m venv .venv && source .venv/bin/activate`\r\n - Instale depend\u00eancias: `pip install -r requirements.txt`\r\n - Rode o app: `python -m ui.core.main`\r\n---\r\n## CLI Detalhado (guia completo)\r\nO CLI acelera tarefas comuns. Abaixo, as op\u00e7\u00f5es e impactos de cada comando.\r\n- new page\r\n - Cria um arquivo em `app/pages/<rota>_page.py` com:\r\n - `PAGE = { route, label, sidebar, order }`\r\n - `build(task_runner=None, theme_service=None)`\r\n - Estrutura com `QScrollArea` pronta para conte\u00fado extenso\r\n - Exemplo:\r\n - `python -m ui.cli new page \"Relatorios\" --route relatorios --label Relat\u00f3rios --order 10 --sidebar`\r\n - Manifesto: faz \u201cupsert\u201d de uma entrada coerente em `app/assets/pages_manifest.json`.\r\n- new subpage\r\n - Gera uma subp\u00e1gina com rota `pai/filho` (ex.: `home/detalhes`).\r\n - Exemplo:\r\n - `python -m ui.cli new subpage \"Detalhes\" --parent home --route detalhes --label Detalhes --order 11 --sidebar`\r\n - Arquivo: `app/pages/home_detalhes_page.py` (rota normalizada).\r\n - Manifesto: atualiza com a nova rota.\r\n- examples\r\n - Cria `examples_widgets_page.py` com usos pr\u00e1ticos de bot\u00f5es, AsyncTaskButton e toasts.\r\n - Exemplo: `python -m ui.cli examples`\r\n - Manifesto: adiciona rota `examples`.\r\n- manifest-update\r\n - Reescreve completamente `app/assets/pages_manifest.json` com base na auto\u2011descoberta de `app.pages.*`.\r\n - \u00datil ap\u00f3s mover/renomear p\u00e1ginas manualmente.\r\n - Exemplo: `python -m ui.cli manifest-update`\r\n- clean-pages\r\n - Remove p\u00e1ginas pr\u00e9\u2011programadas de exemplo e cria uma Home m\u00ednima.\r\n - Exemplo: `python -m ui.cli clean-pages` (ou `--rebuild-manifest` para reconstruir o manifesto via descoberta)\r\nObserva\u00e7\u00f5es importantes\r\n- O nome do arquivo gerado \u00e9 a rota com barras substitu\u00eddas por sublinhados + `_page.py`.\r\n- A descoberta padr\u00e3o importa `app.pages.*`; mantenha p\u00e1ginas dentro de `app/pages`.\r\n---\r\n## Criando P\u00e1ginas Manualmente (sem CLI)\r\n1) Crie um arquivo em `app/pages/minha_pagina_page.py` com:\r\n```\r\nfrom PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea, QFrame\r\nPAGE = { \"route\": \"minha-pagina\", \"label\": \"Minha P\u00e1gina\", \"sidebar\": True, \"order\": 50 }\r\nclass MinhaPaginaPage(QWidget):\r\n def __init__(self, task_runner=None, theme_service=None):\r\n super().__init__()\r\n root = QVBoxLayout(self); root.setContentsMargins(14,14,14,14); root.setSpacing(12)\r\n scroll = QScrollArea(self); scroll.setWidgetResizable(True)\r\n wrap = QFrame(); wrap_l = QVBoxLayout(wrap); wrap_l.setContentsMargins(0,0,0,0); wrap_l.setSpacing(10)\r\n wrap_l.addWidget(QLabel(\"Conte\u00fado da Minha P\u00e1gina\")); wrap_l.addStretch(1)\r\n scroll.setWidget(wrap); root.addWidget(scroll, 1)\r\n def on_route(self, params: dict | None = None):\r\n # Atualize conte\u00fado com base em params quando a rota for ativada\r\n pass\r\ndef build(task_runner=None, theme_service=None) -> QWidget:\r\n return MinhaPaginaPage(task_runner=task_runner, theme_service=theme_service)\r\n```\r\n2) Registre no manifesto (opcional caso confie na auto\u2011descoberta)\r\n```\r\n// app/assets/pages_manifest.json\r\n[\r\n { \"route\": \"home\", \"label\": \"In\u00edcio\", \"sidebar\": true, \"order\": 0,\r\n \"factory\": \"app.pages.home_page:build\" },\r\n { \"route\": \"minha-pagina\", \"label\": \"Minha P\u00e1gina\", \"sidebar\": true, \"order\": 50,\r\n \"factory\": \"app.pages.minha_pagina_page:build\" }\r\n]\r\n```\r\n3) Execute e navegue: a p\u00e1gina aparecer\u00e1 na sidebar e breadcrumbs.\r\n---\r\n## Subp\u00e1ginas Manualmente (sem CLI)\r\n1) Rota e arquivo\r\n- Para uma subp\u00e1gina de `home`, crie `app/pages/home_detalhes_page.py` com `PAGE = { \"route\": \"home/detalhes\", ... }`.\r\n- O breadcrumb mostrar\u00e1 `Home / Detalhes` automaticamente.\r\n2) SideBar e order\r\n- `sidebar: true` para aparecer no menu; `false` para rotas internas.\r\n- Ajuste `order` para ordenar a exibi\u00e7\u00e3o na sidebar.\r\n3) Navega\u00e7\u00e3o program\u00e1tica\r\n```\r\ndef _go(self, path: str):\r\n win = self.window(); r = getattr(win, \"router\", None)\r\n if r: r.go(path)\r\n```\r\n---\r\n## Widgets: Guia Pr\u00e1tico (Cookbook)\r\nBot\u00f5es (Controls.Button / PrimaryButton)\r\n```\r\nfrom ui.widgets.buttons import Controls\r\nbtn = Controls.Button(\"Prim\u00e1rio\", size_preset=\"md\")\r\nbtn.setProperty(\"variant\", \"primary\") # outras: \"chip\", \"ghost\" (via QSS)\r\nbtn.clicked.connect(lambda: print(\"clicado\"))\r\n```\r\nTamanhos e variantes\r\n- `size_preset`: `sm`, `md`, `lg`, `xl`, `char` (um caractere)\r\n- `variant` (QSS): `primary`, `chip`, `ghost` (e outras que seu QSS definir)\r\nBot\u00e3o de comando (TaskRunner)\r\n```\r\nfrom ui.widgets.buttons import command_button\r\nbtn = command_button(\r\n text=\"Sincronizar\",\r\n command_name=\"sync\",\r\n task_runner=my_runner,\r\n payload={\"force\": True},\r\n disable_while_running=True,\r\n lock_after_click=False,\r\n size_preset=\"md\"\r\n)\r\n```\r\nConfirma\u00e7\u00e3o + comando\r\n```\r\nfrom ui.widgets.buttons import confirm_command_button\r\nbtn = confirm_command_button(\r\n text=\"Excluir\",\r\n confirm_msg=\"Tem certeza?\",\r\n command_name=\"delete_item\",\r\n task_runner=my_runner,\r\n payload={\"id\": 123},\r\n size_preset=\"sm\"\r\n)\r\n```\r\nNavegar por rota (route_button)\r\n```\r\nfrom ui.widgets.buttons import route_button\r\ngo = lambda: self.window().router.go(\"home/detalhes\")\r\nbtn = route_button(\"Ir\", go, size_preset=\"sm\")\r\n```\r\nToggle / CheckBox / TextInput / Select\r\n```\r\nt = Controls.Toggle(); t.setChecked(True)\r\nck = Controls.CheckBox(\"Aceito termos\")\r\ninp = Controls.TextInput(\"Digite aqui...\")\r\nsel = Controls.InputList(); sel.addItems([\"A\", \"B\", \"C\"]) \r\n```\r\n\u00cdcone / LinkLabel\r\n```\r\nicon = Controls.IconButton(\"\u22ef\", tooltip=\"Mais\")\r\nlink = Controls.LinkLabel(\"Abrir documenta\u00e7\u00e3o\"); link.clicked.connect(lambda: print(\"abrir\"))\r\n```\r\nPopover de ajuda (hover)\r\n```\r\nfrom ui.widgets.buttons import attach_popover\r\nattach_popover(icon, \"A\u00e7\u00e3o X\", \"Executa a a\u00e7\u00e3o X\", \"Ctrl+X\")\r\n```\r\nExpandir/colapsar painel (ExpandMore)\r\n```\r\nfrom ui.widgets.buttons import Controls\r\npanel = QFrame(); panel.setLayout(QVBoxLayout()); panel.layout().addWidget(QLabel(\"Detalhes\"))\r\nexp = Controls.ExpandMore(panel, text_collapsed=\"Ver mais\", text_expanded=\"Ver menos\")\r\nlayout.addWidget(exp)\r\n```\r\nSlider em modo progresso (n\u00e3o interativo)\r\n```\r\ns = Controls.Slider(); s.setRange(0,100); s.setValue(42); s.setMode(\"progress\")\r\n```\r\nScroll customizado\r\n```\r\nscroll = Controls.ScrollArea(); scroll.setWidget(wrap)\r\n```\r\nAsyncTaskButton (tarefas ass\u00edncronas)\r\n```\r\nfrom ui.widgets.async_button import AsyncTaskButton\r\nab = AsyncTaskButton(\r\n \"Executar\",\r\n task_runner=my_runner,\r\n command_name=\"processar\",\r\n payload={\"limit\": 10},\r\n block_input=True, # opcional: bloqueia com overlay\r\n use_overlay=True,\r\n overlay_message=\"Processando...\",\r\n progress_text=\"Processando\u2026\", progress_kind=\"info\", progress_cancellable=False,\r\n)\r\nab.succeeded.connect(lambda *_: print(\"ok\"))\r\nab.failed.connect(lambda *_: print(\"falha\"))\r\nlayout.addWidget(ab)\r\n```\r\nToasts (simples, a\u00e7\u00e3o e progresso)\r\n```\r\nfrom ui.widgets.toast import show_toast, show_action_toast, ProgressToast\r\nshow_toast(self, \"Salvo com sucesso\", kind=\"ok\")\r\nshow_action_toast(\r\n self, \"Exporta\u00e7\u00e3o\", \"Arquivo pronto.\", kind=\"ok\",\r\n actions=[{\"label\":\"Abrir pasta\",\"command\":\"abrir_pasta\",\"payload\":{}}], persist=True\r\n)\r\npt = ProgressToast.start(self, \"Sincronizando\u2026\", kind=\"info\", cancellable=True)\r\npt.set_indeterminate(True)\r\npt.update(3, 10)\r\npt.finish(True, \"Conclu\u00eddo\")\r\n```\r\nLoadingOverlay manual\r\n```\r\nfrom ui.widgets.loading_overlay import LoadingOverlay\r\nov = LoadingOverlay(self, message=\"Carregando\u2026\", block_input=True)\r\nov.show(); ...; ov.hide()\r\n```\r\nQuick Open program\u00e1tico\r\n```\r\nfrom ui.dialogs.quick_open import QuickOpenDialog\r\ndlg = QuickOpenDialog(self.window().router._pages.keys(), parent=self)\r\ndlg.exec()\r\n```\r\n---\r\n## Personaliza\u00e7\u00e3o Visual & Chrome\r\n- TitleBar: o \u00edcone do app troca conforme o tema; \u00edcones em `app/assets/icons/app/`.\r\n- TopBar: breadcrumb e sino j\u00e1 integrados; badge reflete o Centro de Notifica\u00e7\u00f5es.\r\n- Sidebars: `OverlaySidePanel` (navega\u00e7\u00e3o) e `SettingsSidePanel` (configura\u00e7\u00f5es) estiliz\u00e1veis via QSS.\r\n- PushSidePanel (direita): painel do Centro de Notifica\u00e7\u00f5es com redimensionamento por \u201cgrip\u201d.\r\n---\r\n## Centro de Notifica\u00e7\u00f5es (avan\u00e7ado)\r\nInteraja com o barramento para criar/atualizar/remover entradas:\r\n```\r\nfrom ui.widgets.toast import notification_bus\r\nbus = notification_bus()\r\nbus.addEntry.emit({\r\n \"id\": \"abc123\", \"type\": \"info\", \"title\": \"Processo iniciado\",\r\n \"text\": \"Aguarde\u2026\", \"persist\": True, \"expires_on_finish\": True\r\n})\r\nbus.updateEntry.emit({\"id\": \"abc123\", \"text\": \"50%\"})\r\nbus.finishEntry.emit(\"abc123\")\r\nbus.removeEntry.emit(\"abc123\")\r\n```\r\n---\r\n## Temas: Dicas & Avan\u00e7ado\r\n- Adicione vari\u00e1veis em `*.json` na chave `vars` e fa\u00e7a refer\u00eancia no `base.qss` usando `{{token}}`, `{token}` ou `${token}`.\r\n- Tokens derivados (ex.: `content_bg`) s\u00e3o gerados automaticamente.\r\n- O ThemeService observa `base.qss` e temas; altera\u00e7\u00f5es s\u00e3o aplicadas em tempo real.\r\n- Use o Editor de Temas (painel) para editar e salvar de forma segura (grava\u00e7\u00e3o at\u00f4mica).\r\nExemplo de uso de token no QSS\r\n```\r\nQPushButton[variant=\"chip\"] {\r\n background: {{surface}}; color: {{text}}; border: 1px solid {{box_border}};\r\n}\r\n```\r\n---\r\n## Troubleshooting\r\n- P\u00e1gina n\u00e3o aparece\r\n - Verifique se o m\u00f3dulo est\u00e1 em `app/pages` e exporta `PAGE` e `build()`.\r\n - Cheque `pages_manifest.json` ou rode `python -m ui.cli manifest-update`.\r\n- QSS n\u00e3o aplica\r\n - Verifique `app/assets/qss/base.qss`. O ThemeService emite sinais nas trocas; acompanhe logs.\r\n- Toasts n\u00e3o aparecem\r\n - Confirme se o app est\u00e1 rodando com `AppShell` padr\u00e3o; toasts dependem do shell frameless.\r\n---\r\n## Blueprint de P\u00e1gina (modelo recomendado)\r\nUse este esqueleto como base para novas p\u00e1ginas, mantendo consist\u00eancia visual e t\u00e9cnica.\r\n```python\r\nfrom __future__ import annotations\r\nfrom PySide6.QtWidgets import QWidget, QVBoxLayout, QScrollArea, QFrame, QLabel\r\n# Metadados de rota (sidebar, ordem e r\u00f3tulo)\r\nPAGE = {\r\n \"route\": \"minha-pagina\",\r\n \"label\": \"Minha P\u00e1gina\",\r\n \"sidebar\": True,\r\n \"order\": 50,\r\n}\r\nclass MinhaPaginaPage(QWidget):\r\n def __init__(self, task_runner=None, theme_service=None):\r\n super().__init__()\r\n root = QVBoxLayout(self)\r\n root.setContentsMargins(14, 14, 14, 14)\r\n root.setSpacing(12)\r\n # \u00c1rea rol\u00e1vel para conte\u00fados longos\r\n scroll = QScrollArea(self)\r\n scroll.setWidgetResizable(True)\r\n # Container real do conte\u00fado\r\n wrap = QFrame()\r\n wrap_l = QVBoxLayout(wrap)\r\n wrap_l.setContentsMargins(0, 0, 0, 0)\r\n wrap_l.setSpacing(10)\r\n # Conte\u00fado inicial\r\n wrap_l.addWidget(QLabel(\"Ol\u00e1, mundo!\"))\r\n wrap_l.addStretch(1)\r\n scroll.setWidget(wrap)\r\n root.addWidget(scroll, 1)\r\n # Hook de ciclo de vida: chamado quando a rota \u00e9 ativada\r\n def on_route(self, params: dict | None = None):\r\n # Atualize conte\u00fado/estado a partir de params, se necess\u00e1rio\r\n pass\r\ndef build(task_runner=None, theme_service=None) -> QWidget:\r\n return MinhaPaginaPage(task_runner=task_runner, theme_service=theme_service)\r\n```\r\nBoas pr\u00e1ticas para p\u00e1ginas\r\n- Use `QScrollArea` como container padr\u00e3o para manter UX consistente.\r\n- Centralize estilos no `base.qss`; evite `setStyleSheet` diretamente em widgets.\r\n- Utilize `on_route(params)` para reagir a navega\u00e7\u00e3o em vez de l\u00f3gicas no `__init__`.\r\n- Prefira nomes de rota em kebab-case (ex.: `relatorios-anuais`).\r\n---\r\n## Qualidade de C\u00f3digo (Linting & Style)\r\nFerramentas sugeridas\r\n- Ruff (lint & fix): `pip install ruff` \u2022 `ruff check .` \u2022 `ruff check . --fix`\r\n- Black (format): `pip install black` \u2022 `black .`\r\n- isort (imports): `pip install isort` \u2022 `isort .`\r\n- mypy (tipagem opcional): `pip install mypy` \u2022 `mypy ui app`\r\nDicas de estilo\r\n- Tipagem: anote fun\u00e7\u00f5es e atributos p\u00fablicos; use `Optional[...]` e `| None` onde apropriado.\r\n- Nomes: rotas em kebab-case; m\u00f3dulos/arquivos em snake_case; classes em PascalCase; vari\u00e1veis/fun\u00e7\u00f5es em snake_case.\r\n- Sinais/slots: prefira nomes descritivos (`openNotificationsRequested`, `setUnreadCountRequested`).\r\n- P\u00e1ginas: exponha `PAGE` + `build(...)`; use `on_route(params)` para ciclo de vida.\r\n- QSS: declare variantes com `setProperty(\"variant\", ...)` e tamanhos com `setProperty(\"size\", ...)`.\r\n- Imports: agrupe padr\u00e3o\u2192terceiros\u2192locais; mantenha ordem alfab\u00e9tica (use `isort`).\r\n- Formata\u00e7\u00e3o: 88 colunas (black), quebras leg\u00edveis, evite linhas muito longas.\r\nSobre o arquivo pyproject.toml\r\n- O que \u00e9: um arquivo \u00fanico de configura\u00e7\u00e3o (na raiz do projeto) que centraliza as regras de ferramentas como Black, Ruff, isort e mypy.\r\n- Onde fica: `pyproject.toml` na raiz deste reposit\u00f3rio.\r\n- Por que usar: padroniza estilo e qualidade para todo o time, reduzindo ru\u00eddo em PRs e tornando os comandos previs\u00edveis.\r\n- Como funciona: cada ferramenta l\u00ea suas op\u00e7\u00f5es diretamente do `pyproject.toml`, ent\u00e3o voc\u00ea n\u00e3o precisa passar flags nos comandos.\r\nComandos do dia a dia (usam o pyproject.toml):\r\n```\r\n# Lint (apenas checar)\r\nruff check .\r\n# Lint com corre\u00e7\u00f5es autom\u00e1ticas\r\nruff check . --fix\r\n# Formatar c\u00f3digo (opiniado)\r\nblack .\r\n# Organizar imports\r\nisort .\r\n# Checar tipos (opcional, gradual)\r\nmypy ui app\r\n```\r\nSugest\u00f5es de fluxo\r\n- Para iniciantes: rode `ruff check . --fix` e depois `black .`; se houver import bagun\u00e7ado, rode `isort .`.\r\n- Para quem j\u00e1 domina: configure um pre-commit com `ruff`, `black` e `isort` ou adicione esses passos no seu CI.\r\n",
"bugtrack_url": null,
"license": "Copyright 2025 Jo\u00e3o Vitor de Souza Siqueira\r\n \r\n Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \u201cSoftware\u201d), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\r\n \r\n The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n \r\n THE SOFTWARE IS PROVIDED \u201cAS IS\u201d, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
"summary": "Framework de UI desktop (PySide6) com janela frameless, roteamento e temas",
"version": "0.1.1",
"project_urls": {
"Homepage": "https://github.com/Skkiler/framework-pyside6",
"Repository": "https://github.com/Skkiler/framework-pyside6"
},
"split_keywords": [
"pyside6",
" qt",
" ui",
" desktop",
" framework"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "4531e94e12d72734cd6c3cc3fec5ffbc308d0455a4071061f2e784dc10bbc464",
"md5": "9d1cc26a0a46fa1f7115fb28b3544e43",
"sha256": "9608903191c92f620d3cf54978154dc88f5cd1570018f8354af27aaec5ea5c26"
},
"downloads": -1,
"filename": "ui_fram_skk-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "9d1cc26a0a46fa1f7115fb28b3544e43",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 91403,
"upload_time": "2025-10-24T19:29:00",
"upload_time_iso_8601": "2025-10-24T19:29:00.461382Z",
"url": "https://files.pythonhosted.org/packages/45/31/e94e12d72734cd6c3cc3fec5ffbc308d0455a4071061f2e784dc10bbc464/ui_fram_skk-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "1e6216f0874c5c5f582d953da5f5cb3c087e2f202724d2d7563d5ec58e966460",
"md5": "b87fff116dd4e39907d6b8b3c4704d58",
"sha256": "9854fb98c5a22f00b9623334e24c4160ad88c7c251a426f7d27e44049c994538"
},
"downloads": -1,
"filename": "ui_fram_skk-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "b87fff116dd4e39907d6b8b3c4704d58",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 89083,
"upload_time": "2025-10-24T19:29:03",
"upload_time_iso_8601": "2025-10-24T19:29:03.150623Z",
"url": "https://files.pythonhosted.org/packages/1e/62/16f0874c5c5f582d953da5f5cb3c087e2f202724d2d7563d5ec58e966460/ui_fram_skk-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-24 19:29:03",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Skkiler",
"github_project": "framework-pyside6",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "PySide6",
"specs": []
}
],
"lcname": "ui-fram-skk"
}