dex-framework 0.0.50__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. dex_framework/__init__.py +22 -0
  2. dex_framework/buttons/DEx_ActionButtons.py +212 -0
  3. dex_framework/buttons/DEx_CupertinoButton.py +52 -0
  4. dex_framework/buttons/DEx_CupertinoFilledButton.py +52 -0
  5. dex_framework/buttons/DEx_ElevatedButton.py +57 -0
  6. dex_framework/buttons/DEx_FilledButton.py +52 -0
  7. dex_framework/buttons/DEx_FilledTonalButton.py +52 -0
  8. dex_framework/buttons/DEx_FloatingActionButton.py +52 -0
  9. dex_framework/buttons/DEx_IconButton.py +52 -0
  10. dex_framework/buttons/DEx_MenuItemButton.py +52 -0
  11. dex_framework/buttons/DEx_OutlinedButton.py +52 -0
  12. dex_framework/buttons/DEx_PopupMenuButton.py +52 -0
  13. dex_framework/buttons/DEx_SubmenuButton.py +52 -0
  14. dex_framework/buttons/DEx_TextButton.py +52 -0
  15. dex_framework/buttons/__init__.py +30 -0
  16. dex_framework/canvas/DEx_Canvas.py +50 -0
  17. dex_framework/canvas/__init__.py +3 -0
  18. dex_framework/charts/DEx_BarChart.py +82 -0
  19. dex_framework/charts/DEx_CandlestickChart.py +288 -0
  20. dex_framework/charts/DEx_DonutChart.py +334 -0
  21. dex_framework/charts/DEx_GeoHeatmap.py +305 -0
  22. dex_framework/charts/DEx_HeatmapChart.py +363 -0
  23. dex_framework/charts/DEx_LineChart.py +85 -0
  24. dex_framework/charts/DEx_PieChart.py +79 -0
  25. dex_framework/charts/__init__.py +15 -0
  26. dex_framework/charts/_brazil_states.py +295 -0
  27. dex_framework/core/DEx_Compat.py +45 -0
  28. dex_framework/core/DEx_Config.py +26 -0
  29. dex_framework/core/DEx_Protocol.py +30 -0
  30. dex_framework/core/__init__.py +13 -0
  31. dex_framework/display/DEx_Card.py +50 -0
  32. dex_framework/display/DEx_CircleAvatar.py +50 -0
  33. dex_framework/display/DEx_DataTable.py +50 -0
  34. dex_framework/display/DEx_ExpansionPanel.py +50 -0
  35. dex_framework/display/DEx_ExpansionTile.py +50 -0
  36. dex_framework/display/DEx_GridView.py +50 -0
  37. dex_framework/display/DEx_Icon.py +50 -0
  38. dex_framework/display/DEx_Image.py +50 -0
  39. dex_framework/display/DEx_KpiCard.py +205 -0
  40. dex_framework/display/DEx_ListTile.py +50 -0
  41. dex_framework/display/DEx_ListView.py +50 -0
  42. dex_framework/display/DEx_Markdown.py +50 -0
  43. dex_framework/display/DEx_Text.py +50 -0
  44. dex_framework/display/DEx_VersionBar.py +80 -0
  45. dex_framework/display/__init__.py +21 -0
  46. dex_framework/feedback/DEx_AlertDialog.py +50 -0
  47. dex_framework/feedback/DEx_Badge.py +50 -0
  48. dex_framework/feedback/DEx_Banner.py +50 -0
  49. dex_framework/feedback/DEx_Chip.py +50 -0
  50. dex_framework/feedback/DEx_CupertinoActivityIndicator.py +50 -0
  51. dex_framework/feedback/DEx_CupertinoAlertDialog.py +50 -0
  52. dex_framework/feedback/DEx_ProgressBar.py +50 -0
  53. dex_framework/feedback/DEx_ProgressRing.py +50 -0
  54. dex_framework/feedback/DEx_SnackBar.py +50 -0
  55. dex_framework/feedback/DEx_Tooltip.py +53 -0
  56. dex_framework/feedback/__init__.py +17 -0
  57. dex_framework/input/DEx_Checkbox.py +50 -0
  58. dex_framework/input/DEx_CupertinoCheckbox.py +50 -0
  59. dex_framework/input/DEx_CupertinoSlider.py +50 -0
  60. dex_framework/input/DEx_CupertinoSwitch.py +50 -0
  61. dex_framework/input/DEx_CupertinoTextField.py +56 -0
  62. dex_framework/input/DEx_DatePicker.py +50 -0
  63. dex_framework/input/DEx_Dropdown.py +57 -0
  64. dex_framework/input/DEx_Radio.py +50 -0
  65. dex_framework/input/DEx_RadioGroup.py +50 -0
  66. dex_framework/input/DEx_RangeSlider.py +50 -0
  67. dex_framework/input/DEx_Slider.py +50 -0
  68. dex_framework/input/DEx_Switch.py +50 -0
  69. dex_framework/input/DEx_TextField.py +57 -0
  70. dex_framework/input/DEx_TimePicker.py +50 -0
  71. dex_framework/input/__init__.py +22 -0
  72. dex_framework/interaction/DEx_Dismissible.py +50 -0
  73. dex_framework/interaction/DEx_Draggable.py +50 -0
  74. dex_framework/interaction/DEx_GestureDetector.py +50 -0
  75. dex_framework/interaction/DEx_SelectionArea.py +50 -0
  76. dex_framework/interaction/DEx_TransparentPointer.py +50 -0
  77. dex_framework/interaction/__init__.py +10 -0
  78. dex_framework/layout/DEx_AppShell.py +394 -0
  79. dex_framework/layout/DEx_Column.py +50 -0
  80. dex_framework/layout/DEx_Container.py +50 -0
  81. dex_framework/layout/DEx_Header.py +171 -0
  82. dex_framework/layout/DEx_ResponsiveRow.py +50 -0
  83. dex_framework/layout/DEx_Row.py +50 -0
  84. dex_framework/layout/DEx_SafeArea.py +50 -0
  85. dex_framework/layout/DEx_Stack.py +50 -0
  86. dex_framework/layout/__init__.py +15 -0
  87. dex_framework/maps/DEx_GeoHeatmap.py +703 -0
  88. dex_framework/maps/DEx_Map.py +79 -0
  89. dex_framework/maps/__init__.py +4 -0
  90. dex_framework/media/DEx_Audio.py +80 -0
  91. dex_framework/media/DEx_Lottie.py +82 -0
  92. dex_framework/media/DEx_Video.py +75 -0
  93. dex_framework/media/__init__.py +5 -0
  94. dex_framework/navigation/DEx_AppBar.py +50 -0
  95. dex_framework/navigation/DEx_CupertinoNavigationBar.py +50 -0
  96. dex_framework/navigation/DEx_NavBar.py +531 -0
  97. dex_framework/navigation/DEx_NavigationBar.py +50 -0
  98. dex_framework/navigation/DEx_NavigationDrawer.py +50 -0
  99. dex_framework/navigation/DEx_NavigationRail.py +50 -0
  100. dex_framework/navigation/DEx_Tabs.py +158 -0
  101. dex_framework/navigation/__init__.py +13 -0
  102. dex_framework/overlay/DEx_BottomSheet.py +50 -0
  103. dex_framework/overlay/DEx_CupertinoBottomSheet.py +50 -0
  104. dex_framework/overlay/DEx_Drawer.py +51 -0
  105. dex_framework/overlay/DEx_MenuBar.py +50 -0
  106. dex_framework/overlay/DEx_Pagelet.py +50 -0
  107. dex_framework/overlay/DEx_SearchAnchor.py +67 -0
  108. dex_framework/overlay/DEx_SearchBar.py +50 -0
  109. dex_framework/overlay/__init__.py +12 -0
  110. dex_framework/py.typed +0 -0
  111. dex_framework/scrolling/DEx_DragTarget.py +50 -0
  112. dex_framework/scrolling/DEx_ScrollableControl.py +56 -0
  113. dex_framework/scrolling/__init__.py +4 -0
  114. dex_framework/theming/DEx_BrandThemes.py +1006 -0
  115. dex_framework/theming/DEx_ColorScheme.py +37 -0
  116. dex_framework/theming/DEx_Theme.py +37 -0
  117. dex_framework/theming/__init__.py +5 -0
  118. dex_framework-0.0.50.dist-info/METADATA +217 -0
  119. dex_framework-0.0.50.dist-info/RECORD +122 -0
  120. dex_framework-0.0.50.dist-info/WHEEL +5 -0
  121. dex_framework-0.0.50.dist-info/licenses/LICENSE +21 -0
  122. dex_framework-0.0.50.dist-info/top_level.txt +1 -0
@@ -0,0 +1,52 @@
1
+ """
2
+ DEx_OutlinedButton — DEx-Framework v3.0.0
3
+ Encapsula flet.OutlinedButton com padrões DEx.
4
+
5
+ Uso básico:
6
+ from dex_framework.buttons import DEx_OutlinedButton
7
+ componente = DEx_OutlinedButton(...)
8
+ """
9
+
10
+ from __future__ import annotations
11
+ import flet as ft
12
+ from typing import Any, Callable, Optional
13
+
14
+
15
+ class DEx_OutlinedButton(ft.OutlinedButton):
16
+ """
17
+ Encapsulamento DEx do controle flet.OutlinedButton.
18
+
19
+ Mantém 100% da API original do Flet e adiciona:
20
+ - Valores padrão opinados (brand defaults)
21
+ - Parâmetro `dex_id` para rastreamento
22
+ - Hook `on_dex_ready` disparado após montagem
23
+
24
+ Args:
25
+ dex_id: Identificador único do componente na aplicação.
26
+ on_dex_ready: Callback chamado quando o componente é montado.
27
+ **kwargs: Todos os parâmetros nativos de flet.OutlinedButton.
28
+
29
+ Example:
30
+ >>> btn = DEx_OutlinedButton(
31
+ ... dex_id="exemplo",
32
+ ... )
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ *args: Any,
38
+ dex_id: str = "",
39
+ dex_theme: str = "dell",
40
+ on_dex_ready: Optional[Callable[["DEx_OutlinedButton"], None]] = None,
41
+ **kwargs: Any,
42
+ ) -> None:
43
+ super().__init__(*args, **kwargs)
44
+ self.dex_id: str = dex_id
45
+ self.dex_theme: str = dex_theme
46
+ self._on_dex_ready = on_dex_ready
47
+
48
+ def did_mount(self) -> None:
49
+ """Chamado pelo Flet quando o controle é inserido na árvore."""
50
+ super().did_mount()
51
+ if self._on_dex_ready:
52
+ self._on_dex_ready(self)
@@ -0,0 +1,52 @@
1
+ """
2
+ DEx_PopupMenuButton — DEx-Framework v3.0.0
3
+ Encapsula flet.PopupMenuButton com padrões DEx.
4
+
5
+ Uso básico:
6
+ from dex_framework.buttons import DEx_PopupMenuButton
7
+ componente = DEx_PopupMenuButton(...)
8
+ """
9
+
10
+ from __future__ import annotations
11
+ import flet as ft
12
+ from typing import Any, Callable, Optional
13
+
14
+
15
+ class DEx_PopupMenuButton(ft.PopupMenuButton):
16
+ """
17
+ Encapsulamento DEx do controle flet.PopupMenuButton.
18
+
19
+ Mantém 100% da API original do Flet e adiciona:
20
+ - Valores padrão opinados (brand defaults)
21
+ - Parâmetro `dex_id` para rastreamento
22
+ - Hook `on_dex_ready` disparado após montagem
23
+
24
+ Args:
25
+ dex_id: Identificador único do componente na aplicação.
26
+ on_dex_ready: Callback chamado quando o componente é montado.
27
+ **kwargs: Todos os parâmetros nativos de flet.PopupMenuButton.
28
+
29
+ Example:
30
+ >>> btn = DEx_PopupMenuButton(
31
+ ... dex_id="exemplo",
32
+ ... )
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ *args: Any,
38
+ dex_id: str = "",
39
+ dex_theme: str = "dell",
40
+ on_dex_ready: Optional[Callable[["DEx_PopupMenuButton"], None]] = None,
41
+ **kwargs: Any,
42
+ ) -> None:
43
+ super().__init__(*args, **kwargs)
44
+ self.dex_id: str = dex_id
45
+ self.dex_theme: str = dex_theme
46
+ self._on_dex_ready = on_dex_ready
47
+
48
+ def did_mount(self) -> None:
49
+ """Chamado pelo Flet quando o controle é inserido na árvore."""
50
+ super().did_mount()
51
+ if self._on_dex_ready:
52
+ self._on_dex_ready(self)
@@ -0,0 +1,52 @@
1
+ """
2
+ DEx_SubmenuButton — DEx-Framework v3.0.0
3
+ Encapsula flet.SubmenuButton com padrões DEx.
4
+
5
+ Uso básico:
6
+ from dex_framework.buttons import DEx_SubmenuButton
7
+ componente = DEx_SubmenuButton(...)
8
+ """
9
+
10
+ from __future__ import annotations
11
+ import flet as ft
12
+ from typing import Any, Callable, Optional
13
+
14
+
15
+ class DEx_SubmenuButton(ft.SubmenuButton):
16
+ """
17
+ Encapsulamento DEx do controle flet.SubmenuButton.
18
+
19
+ Mantém 100% da API original do Flet e adiciona:
20
+ - Valores padrão opinados (brand defaults)
21
+ - Parâmetro `dex_id` para rastreamento
22
+ - Hook `on_dex_ready` disparado após montagem
23
+
24
+ Args:
25
+ dex_id: Identificador único do componente na aplicação.
26
+ on_dex_ready: Callback chamado quando o componente é montado.
27
+ **kwargs: Todos os parâmetros nativos de flet.SubmenuButton.
28
+
29
+ Example:
30
+ >>> btn = DEx_SubmenuButton(
31
+ ... dex_id="exemplo",
32
+ ... )
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ *args: Any,
38
+ dex_id: str = "",
39
+ dex_theme: str = "dell",
40
+ on_dex_ready: Optional[Callable[["DEx_SubmenuButton"], None]] = None,
41
+ **kwargs: Any,
42
+ ) -> None:
43
+ super().__init__(*args, **kwargs)
44
+ self.dex_id: str = dex_id
45
+ self.dex_theme: str = dex_theme
46
+ self._on_dex_ready = on_dex_ready
47
+
48
+ def did_mount(self) -> None:
49
+ """Chamado pelo Flet quando o controle é inserido na árvore."""
50
+ super().did_mount()
51
+ if self._on_dex_ready:
52
+ self._on_dex_ready(self)
@@ -0,0 +1,52 @@
1
+ """
2
+ DEx_TextButton — DEx-Framework v3.0.0
3
+ Encapsula flet.TextButton com padrões DEx.
4
+
5
+ Uso básico:
6
+ from dex_framework.buttons import DEx_TextButton
7
+ componente = DEx_TextButton(...)
8
+ """
9
+
10
+ from __future__ import annotations
11
+ import flet as ft
12
+ from typing import Any, Callable, Optional
13
+
14
+
15
+ class DEx_TextButton(ft.TextButton):
16
+ """
17
+ Encapsulamento DEx do controle flet.TextButton.
18
+
19
+ Mantém 100% da API original do Flet e adiciona:
20
+ - Valores padrão opinados (brand defaults)
21
+ - Parâmetro `dex_id` para rastreamento
22
+ - Hook `on_dex_ready` disparado após montagem
23
+
24
+ Args:
25
+ dex_id: Identificador único do componente na aplicação.
26
+ on_dex_ready: Callback chamado quando o componente é montado.
27
+ **kwargs: Todos os parâmetros nativos de flet.TextButton.
28
+
29
+ Example:
30
+ >>> btn = DEx_TextButton(
31
+ ... dex_id="exemplo",
32
+ ... )
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ *args: Any,
38
+ dex_id: str = "",
39
+ dex_theme: str = "dell",
40
+ on_dex_ready: Optional[Callable[["DEx_TextButton"], None]] = None,
41
+ **kwargs: Any,
42
+ ) -> None:
43
+ super().__init__(*args, **kwargs)
44
+ self.dex_id: str = dex_id
45
+ self.dex_theme: str = dex_theme
46
+ self._on_dex_ready = on_dex_ready
47
+
48
+ def did_mount(self) -> None:
49
+ """Chamado pelo Flet quando o controle é inserido na árvore."""
50
+ super().did_mount()
51
+ if self._on_dex_ready:
52
+ self._on_dex_ready(self)
@@ -0,0 +1,30 @@
1
+ from .DEx_ElevatedButton import DEx_ElevatedButton
2
+ from .DEx_FilledButton import DEx_FilledButton
3
+ from .DEx_FilledTonalButton import DEx_FilledTonalButton
4
+ from .DEx_OutlinedButton import DEx_OutlinedButton
5
+ from .DEx_TextButton import DEx_TextButton
6
+ from .DEx_IconButton import DEx_IconButton
7
+ from .DEx_FloatingActionButton import DEx_FloatingActionButton
8
+ from .DEx_PopupMenuButton import DEx_PopupMenuButton
9
+ from .DEx_MenuItemButton import DEx_MenuItemButton
10
+ from .DEx_SubmenuButton import DEx_SubmenuButton
11
+ from .DEx_CupertinoButton import DEx_CupertinoButton
12
+ from .DEx_CupertinoFilledButton import DEx_CupertinoFilledButton
13
+ from .DEx_ActionButtons import DEx_ActionButton, DEx_ActionButtons
14
+
15
+ __all__ = [
16
+ "DEx_ElevatedButton",
17
+ "DEx_FilledButton",
18
+ "DEx_FilledTonalButton",
19
+ "DEx_OutlinedButton",
20
+ "DEx_TextButton",
21
+ "DEx_IconButton",
22
+ "DEx_FloatingActionButton",
23
+ "DEx_PopupMenuButton",
24
+ "DEx_MenuItemButton",
25
+ "DEx_SubmenuButton",
26
+ "DEx_CupertinoButton",
27
+ "DEx_CupertinoFilledButton",
28
+ "DEx_ActionButton",
29
+ "DEx_ActionButtons",
30
+ ]
@@ -0,0 +1,50 @@
1
+ """
2
+ DEx_Canvas — DEx-Framework v3.0.0
3
+ Encapsula flet.canvas.Canvas com padrões DEx.
4
+
5
+ Uso básico:
6
+ from dex_framework.canvas import DEx_Canvas
7
+ componente = DEx_Canvas(...)
8
+ """
9
+
10
+ from __future__ import annotations
11
+ import flet as ft
12
+ import flet.canvas as cv
13
+ from typing import Any, Callable, Optional
14
+
15
+
16
+ class DEx_Canvas(cv.Canvas):
17
+ """
18
+ Encapsulamento DEx do controle flet.canvas.Canvas.
19
+
20
+ Mantém 100% da API original do Flet e adiciona:
21
+ - Parâmetro `dex_id` para rastreamento
22
+ - Hook `on_dex_ready` disparado após montagem
23
+
24
+ Args:
25
+ dex_id: Identificador único do componente na aplicação.
26
+ on_dex_ready: Callback chamado quando o componente é montado.
27
+ **kwargs: Todos os parâmetros nativos de flet.canvas.Canvas.
28
+
29
+ Example:
30
+ >>> canvas = DEx_Canvas(dex_id="meu_canvas")
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ *args: Any,
36
+ dex_id: str = "",
37
+ dex_theme: str = "dell",
38
+ on_dex_ready: Optional[Callable[["DEx_Canvas"], None]] = None,
39
+ **kwargs: Any,
40
+ ) -> None:
41
+ super().__init__(*args, **kwargs)
42
+ self.dex_id: str = dex_id
43
+ self.dex_theme: str = dex_theme
44
+ self._on_dex_ready = on_dex_ready
45
+
46
+ def did_mount(self) -> None:
47
+ """Chamado pelo Flet quando o controle é inserido na árvore."""
48
+ super().did_mount()
49
+ if self._on_dex_ready:
50
+ self._on_dex_ready(self)
@@ -0,0 +1,3 @@
1
+ from .DEx_Canvas import DEx_Canvas
2
+
3
+ __all__ = ["DEx_Canvas"]
@@ -0,0 +1,82 @@
1
+ """
2
+ DEx_BarChart — DEx-Framework v3.0.0
3
+ Stub: flet.BarChart não está disponível no flet 0.84.0 base.
4
+ Requer pacote adicional (flet-charts).
5
+
6
+ Uso básico:
7
+ from dex_framework.charts import DEx_BarChart
8
+ componente = DEx_BarChart(bar_groups=[...], dex_id="chart")
9
+ """
10
+
11
+ from __future__ import annotations
12
+ import flet as ft
13
+ from typing import Any, Callable, List, Optional
14
+
15
+
16
+ class DEx_BarChart(ft.Container):
17
+ """
18
+ Stub DEx para BarChart.
19
+
20
+ flet.BarChart não está disponível no flet 0.84.0 padrão.
21
+ Requer instalação do pacote flet-charts.
22
+ Este stub preserva a interface sem quebrar imports.
23
+
24
+ Args:
25
+ bar_groups: Lista de BarChartGroup com dados.
26
+ max_y: Valor máximo do eixo Y.
27
+ left_axis: Configuração do eixo esquerdo.
28
+ bottom_axis: Configuração do eixo inferior.
29
+ dex_id: Identificador único do componente na aplicação.
30
+ on_dex_ready: Callback chamado quando o componente é montado.
31
+ **kwargs: Parâmetros adicionais.
32
+
33
+ Example:
34
+ >>> chart = DEx_BarChart(bar_groups=[...], dex_id="vendas")
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ *args: Any,
40
+ bar_groups: Optional[List[Any]] = None,
41
+ max_y: Optional[float] = None,
42
+ left_axis: Any = None,
43
+ bottom_axis: Any = None,
44
+ dex_id: str = "",
45
+ dex_theme: str = "dell",
46
+ on_dex_ready: Optional[Callable[["DEx_BarChart"], None]] = None,
47
+ **kwargs: Any,
48
+ ) -> None:
49
+ self.dex_id: str = dex_id
50
+ self.dex_theme: str = dex_theme
51
+ self._on_dex_ready = on_dex_ready
52
+ self.bar_groups = bar_groups or []
53
+ self.max_y = max_y
54
+ self.left_axis = left_axis
55
+ self.bottom_axis = bottom_axis
56
+ kwargs.pop("content", None)
57
+ kwargs.pop("bgcolor", None)
58
+ kwargs.pop("alignment", None)
59
+ kwargs.pop("border", None)
60
+ super().__init__(
61
+ content=ft.Column(
62
+ controls=[
63
+ ft.Icon(ft.Icons.BAR_CHART, size=48, color=ft.Colors.GREY_400),
64
+ ft.Text(
65
+ "[BarChart stub — instale flet-charts]",
66
+ color=ft.Colors.GREY,
67
+ italic=True,
68
+ ),
69
+ ],
70
+ horizontal_alignment=ft.CrossAxisAlignment.CENTER,
71
+ ),
72
+ bgcolor=ft.Colors.GREY_50,
73
+ alignment=ft.Alignment(0, 0),
74
+ border=ft.Border.all(1, ft.Colors.GREY_300),
75
+ **kwargs,
76
+ )
77
+
78
+ def did_mount(self) -> None:
79
+ """Chamado pelo Flet quando o controle é inserido na árvore."""
80
+ super().did_mount()
81
+ if self._on_dex_ready:
82
+ self._on_dex_ready(self)
@@ -0,0 +1,288 @@
1
+ """
2
+ DEx_CandlestickChart — DEx-Framework v3.0.0
3
+ Gráfico de candlestick (OHLC) desenhado nativamente via ft.canvas.
4
+
5
+ Não requer dependências externas — usa apenas flet.canvas disponível no
6
+ flet 0.84.0 padrão.
7
+
8
+ Uso básico:
9
+ from dex_framework.charts import DEx_CandlestickChart, DEx_Candle
10
+
11
+ candles = [
12
+ DEx_Candle("Jan", open=100, high=120, low=90, close=115),
13
+ DEx_Candle("Fev", open=115, high=130, low=110, close=108),
14
+ ]
15
+ chart = DEx_CandlestickChart(candles=candles, dex_id="chart_ohlc")
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import flet as ft
21
+ import flet.canvas as cv
22
+ from dataclasses import dataclass
23
+ from typing import Any, Callable, List, Optional
24
+
25
+
26
+ # ── Modelo de dado ────────────────────────────────────────────────────────────
27
+
28
+ @dataclass
29
+ class DEx_Candle:
30
+ """
31
+ Representa uma vela (candle) OHLC.
32
+
33
+ Args:
34
+ label: Rótulo exibido no eixo X (ex: "Jan", "2024").
35
+ open: Preço / valor de abertura.
36
+ high: Máxima do período.
37
+ low: Mínima do período.
38
+ close: Preço / valor de fechamento.
39
+ """
40
+ label: str
41
+ open: float
42
+ high: float
43
+ low: float
44
+ close: float
45
+
46
+
47
+ # ── Componente ────────────────────────────────────────────────────────────────
48
+
49
+ class DEx_CandlestickChart(ft.Container):
50
+ """
51
+ Gráfico de candlestick desenhado nativamente via ft.canvas.
52
+
53
+ Cada vela exibe wick (pavio) e corpo colorido:
54
+ - Verde → fechamento acima da abertura (bullish)
55
+ - Vermelho → fechamento abaixo da abertura (bearish)
56
+ - Cinza → fechamento igual à abertura (neutral)
57
+
58
+ Recursos:
59
+ - Escala Y automática com padding
60
+ - Grade horizontal configurável
61
+ - Rótulos nos eixos X e Y
62
+ - Título opcional
63
+ - Largura das velas proporcional ao slot disponível
64
+ - Sem dependências externas
65
+
66
+ Args:
67
+ candles: Lista de :class:`DEx_Candle` com os dados OHLC.
68
+ title: Título exibido no topo do gráfico.
69
+ width: Largura total do gráfico em pixels.
70
+ height: Altura total do gráfico em pixels.
71
+ candle_width_ratio: Fração do slot ocupada pelo corpo (0.0–1.0).
72
+ bullish_color: Cor da vela de alta.
73
+ bearish_color: Cor da vela de baixa.
74
+ neutral_color: Cor da vela neutra.
75
+ grid_color: Cor das linhas de grade.
76
+ show_grid: Exibe ou oculta a grade horizontal.
77
+ y_decimals: Casas decimais dos rótulos do eixo Y.
78
+ dex_id: Identificador único do componente.
79
+ dex_theme: Nome do tema de marca.
80
+ on_dex_ready: Callback chamado após montagem.
81
+ **kwargs: Parâmetros adicionais repassados a ``ft.Container``.
82
+
83
+ Example:
84
+ >>> chart = DEx_CandlestickChart(
85
+ ... candles=[DEx_Candle("Jan", 100, 120, 90, 115)],
86
+ ... title="OHLC Mensal",
87
+ ... dex_id="chart_ohlc",
88
+ ... )
89
+ """
90
+
91
+ _MARGIN_LEFT = 62
92
+ _MARGIN_RIGHT = 14
93
+ _MARGIN_TOP = 20
94
+ _MARGIN_BOTTOM = 30
95
+ _GRID_LINES = 5
96
+
97
+ def __init__(
98
+ self,
99
+ candles: List[DEx_Candle],
100
+ *,
101
+ title: str = "",
102
+ width: float = 600,
103
+ height: float = 320,
104
+ candle_width_ratio: float = 0.55,
105
+ bullish_color: str = ft.Colors.GREEN_600,
106
+ bearish_color: str = ft.Colors.RED_500,
107
+ neutral_color: str = ft.Colors.BLUE_GREY_400,
108
+ grid_color: str = ft.Colors.OUTLINE_VARIANT,
109
+ show_grid: bool = True,
110
+ y_decimals: int = 0,
111
+ # DEx
112
+ dex_id: str = "",
113
+ dex_theme: str = "dell",
114
+ on_dex_ready: Optional[Callable[["DEx_CandlestickChart"], None]] = None,
115
+ **kwargs: Any,
116
+ ) -> None:
117
+ self._candles = candles
118
+ self._title = title
119
+ self._w = width
120
+ self._h = height
121
+ self._candle_ratio = max(0.1, min(candle_width_ratio, 0.95))
122
+ self._bullish = bullish_color
123
+ self._bearish = bearish_color
124
+ self._neutral = neutral_color
125
+ self._grid_color = grid_color
126
+ self._show_grid = show_grid
127
+ self._y_dec = y_decimals
128
+
129
+ self.dex_id = dex_id
130
+ self.dex_theme = dex_theme
131
+ self._on_dex_ready = on_dex_ready
132
+
133
+ kwargs.pop("content", None)
134
+
135
+ super().__init__(
136
+ width=width,
137
+ height=height,
138
+ content=self._build_canvas(),
139
+ **kwargs,
140
+ )
141
+
142
+ # ── Helpers de coordenadas ────────────────────────────────────────────────
143
+
144
+ def _y_range(self) -> tuple[float, float]:
145
+ """Calcula min/max com padding de 8%."""
146
+ lows = [c.low for c in self._candles]
147
+ highs = [c.high for c in self._candles]
148
+ lo, hi = min(lows), max(highs)
149
+ pad = (hi - lo) * 0.08 or abs(hi) * 0.05 or 1.0
150
+ return lo - pad, hi + pad
151
+
152
+ def _to_y(self, value: float, lo: float, hi: float) -> float:
153
+ chart_h = self._h - self._MARGIN_TOP - self._MARGIN_BOTTOM
154
+ ratio = (value - lo) / (hi - lo) if hi != lo else 0.5
155
+ return self._MARGIN_TOP + chart_h * (1.0 - ratio)
156
+
157
+ def _slot(self, i: int) -> tuple[float, float]:
158
+ """Retorna (centro_x, largura_slot) para o índice i."""
159
+ n = len(self._candles)
160
+ chart_w = self._w - self._MARGIN_LEFT - self._MARGIN_RIGHT
161
+ sw = chart_w / n
162
+ cx = self._MARGIN_LEFT + sw * i + sw / 2.0
163
+ return cx, sw
164
+
165
+ # ── Construção do canvas ──────────────────────────────────────────────────
166
+
167
+ def _build_canvas(self) -> cv.Canvas:
168
+ shapes: list = []
169
+
170
+ lbl_style = ft.TextStyle(size=9, color=ft.Colors.ON_SURFACE_VARIANT)
171
+ title_style = ft.TextStyle(size=11, weight=ft.FontWeight.W_600,
172
+ color=ft.Colors.ON_SURFACE)
173
+
174
+ if not self._candles:
175
+ shapes.append(cv.Text(
176
+ x=self._w / 2, y=self._h / 2,
177
+ value="Sem dados",
178
+ style=ft.TextStyle(size=13, color=ft.Colors.ON_SURFACE_VARIANT),
179
+ alignment=ft.Alignment(0, 0),
180
+ ))
181
+ return cv.Canvas(shapes=shapes, width=self._w, height=self._h)
182
+
183
+ lo, hi = self._y_range()
184
+ chart_h = self._h - self._MARGIN_TOP - self._MARGIN_BOTTOM
185
+ chart_w = self._w - self._MARGIN_LEFT - self._MARGIN_RIGHT
186
+
187
+ # ── Título ────────────────────────────────────────────────────────
188
+ if self._title:
189
+ shapes.append(cv.Text(
190
+ x=self._MARGIN_LEFT + chart_w / 2,
191
+ y=4,
192
+ value=self._title,
193
+ style=title_style,
194
+ alignment=ft.Alignment(0, -1),
195
+ ))
196
+
197
+ # ── Grade + rótulos Y ─────────────────────────────────────────────
198
+ for i in range(self._GRID_LINES + 1):
199
+ ratio = i / self._GRID_LINES
200
+ gy = self._MARGIN_TOP + chart_h * (1.0 - ratio)
201
+ val = lo + (hi - lo) * ratio
202
+
203
+ if self._show_grid:
204
+ shapes.append(cv.Line(
205
+ x1=self._MARGIN_LEFT, y1=gy,
206
+ x2=self._MARGIN_LEFT + chart_w, y2=gy,
207
+ paint=ft.Paint(
208
+ color=self._grid_color,
209
+ stroke_width=1.0 if i == 0 else 0.5,
210
+ ),
211
+ ))
212
+
213
+ fmt = f"{val:,.{self._y_dec}f}"
214
+ shapes.append(cv.Text(
215
+ x=self._MARGIN_LEFT - 5, y=gy,
216
+ value=fmt,
217
+ style=lbl_style,
218
+ alignment=ft.Alignment(1, 0),
219
+ ))
220
+
221
+ # ── Eixo esquerdo (borda) ─────────────────────────────────────────
222
+ shapes.append(cv.Line(
223
+ x1=self._MARGIN_LEFT, y1=self._MARGIN_TOP,
224
+ x2=self._MARGIN_LEFT, y2=self._MARGIN_TOP + chart_h,
225
+ paint=ft.Paint(color=ft.Colors.OUTLINE_VARIANT, stroke_width=1),
226
+ ))
227
+
228
+ # ── Velas ─────────────────────────────────────────────────────────
229
+ for i, c in enumerate(self._candles):
230
+ cx, sw = self._slot(i)
231
+ cw = max(sw * self._candle_ratio, 2.0)
232
+
233
+ oy = self._to_y(c.open, lo, hi)
234
+ cly = self._to_y(c.close, lo, hi)
235
+ hy = self._to_y(c.high, lo, hi)
236
+ ly = self._to_y(c.low, lo, hi)
237
+
238
+ # Cor da vela
239
+ if c.close > c.open:
240
+ color = self._bullish
241
+ elif c.close < c.open:
242
+ color = self._bearish
243
+ else:
244
+ color = self._neutral
245
+
246
+ # Pavio (wick)
247
+ shapes.append(cv.Line(
248
+ x1=cx, y1=hy, x2=cx, y2=ly,
249
+ paint=ft.Paint(color=color, stroke_width=1.5),
250
+ ))
251
+
252
+ # Corpo (body)
253
+ body_top = min(oy, cly)
254
+ body_h = max(abs(cly - oy), 2.0)
255
+
256
+ shapes.append(cv.Rect(
257
+ x=cx - cw / 2, y=body_top,
258
+ width=cw, height=body_h,
259
+ paint=ft.Paint(color=color, style=ft.PaintingStyle.FILL),
260
+ ))
261
+ shapes.append(cv.Rect(
262
+ x=cx - cw / 2, y=body_top,
263
+ width=cw, height=body_h,
264
+ paint=ft.Paint(
265
+ color=ft.Colors.with_opacity(0.25, ft.Colors.ON_SURFACE),
266
+ stroke_width=0.5,
267
+ style=ft.PaintingStyle.STROKE,
268
+ ),
269
+ ))
270
+
271
+ # Rótulo X
272
+ shapes.append(cv.Text(
273
+ x=cx,
274
+ y=self._MARGIN_TOP + chart_h + 6,
275
+ value=c.label,
276
+ style=lbl_style,
277
+ alignment=ft.Alignment(0, -1),
278
+ ))
279
+
280
+ return cv.Canvas(shapes=shapes, width=self._w, height=self._h)
281
+
282
+ # ── Ciclo de vida DEx ─────────────────────────────────────────────────────
283
+
284
+ def did_mount(self) -> None:
285
+ """Chamado pelo Flet quando o controle é inserido na árvore."""
286
+ super().did_mount()
287
+ if self._on_dex_ready:
288
+ self._on_dex_ready(self)