cronixui 1.0.5 → 1.1.0
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.
- package/README.md +35 -5
- package/package.json +21 -3
- package/packages/go/cronixui/cronixui.go +784 -237
- package/packages/go/cronixui/go.mod +34 -9
- package/packages/go/cronixui/go.sum +666 -0
- package/packages/python/cronixui/__init__.py +131 -1
- package/packages/python/cronixui/alert.py +61 -0
- package/packages/python/cronixui/avatar.py +50 -0
- package/packages/python/cronixui/badge.py +46 -0
- package/packages/python/cronixui/button.py +64 -0
- package/packages/python/cronixui/card.py +62 -0
- package/packages/python/cronixui/form.py +255 -0
- package/packages/python/cronixui/layout.py +143 -0
- package/packages/python/cronixui/list.py +51 -0
- package/packages/python/cronixui/loading.py +36 -0
- package/packages/python/cronixui/progress.py +90 -0
- package/packages/python/cronixui/table.py +48 -0
- package/packages/python/cronixui/tokens.py +200 -0
- package/packages/python/cronixui/tooltip.py +28 -0
- package/packages/react/src/components/Accordion.tsx +82 -0
- package/packages/react/src/components/Alert.tsx +80 -0
- package/packages/react/src/components/Avatar.tsx +54 -0
- package/packages/react/src/components/Badge.tsx +32 -0
- package/packages/react/src/components/Breadcrumb.tsx +50 -0
- package/packages/react/src/components/Button.tsx +47 -0
- package/packages/react/src/components/Card.tsx +69 -0
- package/packages/react/src/components/Checkbox.tsx +30 -0
- package/packages/react/src/components/CommandPalette.tsx +131 -0
- package/packages/react/src/components/Container.tsx +26 -0
- package/packages/react/src/components/Dropdown.tsx +88 -0
- package/packages/react/src/components/FileInput.tsx +86 -0
- package/packages/react/src/components/Footer.tsx +36 -0
- package/packages/react/src/components/FormGroup.tsx +36 -0
- package/packages/react/src/components/Header.tsx +29 -0
- package/packages/react/src/components/Input.tsx +54 -0
- package/packages/react/src/components/List.tsx +55 -0
- package/packages/react/src/components/Modal.tsx +89 -0
- package/packages/react/src/components/Nav.tsx +63 -0
- package/packages/react/src/components/Pagination.tsx +107 -0
- package/packages/react/src/components/Progress.tsx +49 -0
- package/packages/react/src/components/Radio.tsx +64 -0
- package/packages/react/src/components/Search.tsx +95 -0
- package/packages/react/src/components/Select.tsx +41 -0
- package/packages/react/src/components/Sidebar.tsx +64 -0
- package/packages/react/src/components/Skeleton.tsx +39 -0
- package/packages/react/src/components/Slider.tsx +32 -0
- package/packages/react/src/components/Spinner.tsx +24 -0
- package/packages/react/src/components/Stack.tsx +69 -0
- package/packages/react/src/components/Stat.tsx +35 -0
- package/packages/react/src/components/Table.tsx +90 -0
- package/packages/react/src/components/Tabs.tsx +85 -0
- package/packages/react/src/components/Tag.tsx +30 -0
- package/packages/react/src/components/Textarea.tsx +21 -0
- package/packages/react/src/components/Toast.tsx +134 -0
- package/packages/react/src/components/Toggle.tsx +58 -0
- package/packages/react/src/components/Tooltip.tsx +31 -0
- package/packages/react/src/components/Typography.tsx +66 -0
- package/packages/react/src/index.ts +40 -0
- package/packages/react/src/styles.css +2039 -0
- package/packages/react/src/tokens/index.ts +94 -0
- package/packages/rust/cronixui/src/colors.rs +135 -0
- package/packages/rust/cronixui/src/components/accordion.rs +47 -0
- package/packages/rust/cronixui/src/components/alert.rs +95 -0
- package/packages/rust/cronixui/src/components/avatar.rs +85 -0
- package/packages/rust/cronixui/src/components/badge.rs +35 -0
- package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -0
- package/packages/rust/cronixui/src/components/button.rs +70 -0
- package/packages/rust/cronixui/src/components/card.rs +259 -0
- package/packages/rust/cronixui/src/components/command_palette.rs +254 -0
- package/packages/rust/cronixui/src/components/dropdown.rs +179 -0
- package/packages/rust/cronixui/src/components/file_input.rs +74 -0
- package/packages/rust/cronixui/src/components/input.rs +21 -0
- package/packages/rust/cronixui/src/components/list.rs +38 -0
- package/packages/rust/cronixui/src/components/mod.rs +51 -0
- package/packages/rust/cronixui/src/{modal.rs → components/modal.rs} +15 -1
- package/packages/rust/cronixui/src/components/nav.rs +19 -0
- package/packages/rust/cronixui/src/{pagination.rs → components/pagination.rs} +14 -13
- package/packages/rust/cronixui/src/components/progress.rs +50 -0
- package/packages/rust/cronixui/src/components/search.rs +185 -0
- package/packages/rust/cronixui/src/components/skeleton.rs +63 -0
- package/packages/rust/cronixui/src/components/spinner.rs +21 -0
- package/packages/rust/cronixui/src/components/table.rs +56 -0
- package/packages/rust/cronixui/src/components/tabs.rs +43 -0
- package/packages/rust/cronixui/src/components/toast.rs +69 -0
- package/packages/rust/cronixui/src/{toggle.rs → components/toggle.rs} +7 -5
- package/packages/rust/cronixui/src/components/tooltip.rs +11 -0
- package/packages/rust/cronixui/src/lib.rs +111 -62
- package/packages/rust/cronixui/src/tokens.rs +107 -0
- package/packages/web/src/tokens.ts +120 -0
- package/packages/web/src/variables.css +81 -81
- package/packages/python/cronixui/pyproject.toml +0 -11
- package/packages/react/src/components/Accordion.jsx +0 -50
- package/packages/react/src/components/Alert.jsx +0 -62
- package/packages/react/src/components/Avatar.jsx +0 -34
- package/packages/react/src/components/Badge.jsx +0 -15
- package/packages/react/src/components/Breadcrumb.jsx +0 -27
- package/packages/react/src/components/Button.jsx +0 -21
- package/packages/react/src/components/Card.jsx +0 -23
- package/packages/react/src/components/Checkbox.jsx +0 -27
- package/packages/react/src/components/CommandPalette.jsx +0 -93
- package/packages/react/src/components/Dropdown.jsx +0 -48
- package/packages/react/src/components/FileInput.jsx +0 -44
- package/packages/react/src/components/Input.jsx +0 -22
- package/packages/react/src/components/List.jsx +0 -29
- package/packages/react/src/components/Modal.jsx +0 -65
- package/packages/react/src/components/Nav.jsx +0 -50
- package/packages/react/src/components/Pagination.jsx +0 -81
- package/packages/react/src/components/Progress.jsx +0 -23
- package/packages/react/src/components/Radio.jsx +0 -50
- package/packages/react/src/components/Search.jsx +0 -70
- package/packages/react/src/components/Select.jsx +0 -33
- package/packages/react/src/components/Skeleton.jsx +0 -15
- package/packages/react/src/components/Slider.jsx +0 -29
- package/packages/react/src/components/Spinner.jsx +0 -5
- package/packages/react/src/components/Stat.jsx +0 -19
- package/packages/react/src/components/Table.jsx +0 -48
- package/packages/react/src/components/Tabs.jsx +0 -65
- package/packages/react/src/components/Tag.jsx +0 -19
- package/packages/react/src/components/Textarea.jsx +0 -17
- package/packages/react/src/components/Toast.jsx +0 -78
- package/packages/react/src/components/Toggle.jsx +0 -34
- package/packages/react/src/components/Tooltip.jsx +0 -12
- package/packages/react/src/index.d.ts +0 -103
- package/packages/react/src/index.js +0 -33
- package/packages/rust/cronixui/src/accordion.rs +0 -49
- package/packages/rust/cronixui/src/command_palette.rs +0 -62
- package/packages/rust/cronixui/src/dropdown.rs +0 -31
- package/packages/rust/cronixui/src/search.rs +0 -49
- package/packages/rust/cronixui/src/tabs.rs +0 -23
- package/packages/rust/cronixui/src/toast.rs +0 -70
|
@@ -1,379 +1,926 @@
|
|
|
1
1
|
package cronixui
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import (
|
|
4
|
+
"image/color"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
"fyne.io/fyne/v2"
|
|
8
|
+
"fyne.io/fyne/v2/app"
|
|
9
|
+
"fyne.io/fyne/v2/canvas"
|
|
10
|
+
"fyne.io/fyne/v2/container"
|
|
11
|
+
"fyne.io/fyne/v2/layout"
|
|
12
|
+
"fyne.io/fyne/v2/theme"
|
|
13
|
+
"fyne.io/fyne/v2/widget"
|
|
14
|
+
)
|
|
4
15
|
|
|
5
|
-
|
|
6
|
-
|
|
16
|
+
const Version = "1.0.6"
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// DESIGN TOKENS
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
type Colors struct {
|
|
23
|
+
BG color.Color
|
|
24
|
+
Surface color.Color
|
|
25
|
+
Surface2 color.Color
|
|
26
|
+
Surface3 color.Color
|
|
27
|
+
Surface4 color.Color
|
|
28
|
+
Text color.Color
|
|
29
|
+
TextMuted color.Color
|
|
30
|
+
TextDim color.Color
|
|
31
|
+
Accent color.Color
|
|
32
|
+
AccentHover color.Color
|
|
33
|
+
AccentLight color.Color
|
|
34
|
+
AccentGlow color.Color
|
|
35
|
+
AccentText color.Color
|
|
36
|
+
Success color.Color
|
|
37
|
+
SuccessBorder color.Color
|
|
38
|
+
SuccessText color.Color
|
|
39
|
+
Warning color.Color
|
|
40
|
+
WarningBorder color.Color
|
|
41
|
+
WarningText color.Color
|
|
42
|
+
Error color.Color
|
|
43
|
+
ErrorBorder color.Color
|
|
44
|
+
ErrorText color.Color
|
|
45
|
+
Info color.Color
|
|
46
|
+
InfoBorder color.Color
|
|
47
|
+
InfoText color.Color
|
|
48
|
+
Border color.Color
|
|
49
|
+
BorderHover color.Color
|
|
50
|
+
BorderFocus color.Color
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func DefaultColors() *Colors {
|
|
54
|
+
return &Colors{
|
|
55
|
+
BG: color.RGBA{R: 10, G: 10, B: 10, A: 255},
|
|
56
|
+
Surface: color.RGBA{R: 17, G: 17, B: 17, A: 255},
|
|
57
|
+
Surface2: color.RGBA{R: 26, G: 26, B: 26, A: 255},
|
|
58
|
+
Surface3: color.RGBA{R: 34, G: 34, B: 34, A: 255},
|
|
59
|
+
Surface4: color.RGBA{R: 42, G: 42, B: 42, A: 255},
|
|
60
|
+
Text: color.RGBA{R: 240, G: 237, B: 232, A: 255},
|
|
61
|
+
TextMuted: color.RGBA{R: 240, G: 237, B: 232, A: 128},
|
|
62
|
+
TextDim: color.RGBA{R: 240, G: 237, B: 232, A: 64},
|
|
63
|
+
Accent: color.RGBA{R: 107, G: 35, B: 35, A: 255},
|
|
64
|
+
AccentHover: color.RGBA{R: 125, G: 42, B: 42, A: 255},
|
|
65
|
+
AccentLight: color.RGBA{R: 138, G: 53, B: 53, A: 255},
|
|
66
|
+
AccentGlow: color.RGBA{R: 107, G: 35, B: 35, A: 77},
|
|
67
|
+
AccentText: color.RGBA{R: 201, G: 122, B: 122, A: 255},
|
|
68
|
+
Success: color.RGBA{R: 30, G: 80, B: 40, A: 255},
|
|
69
|
+
SuccessBorder: color.RGBA{R: 60, G: 140, B: 70, A: 102},
|
|
70
|
+
SuccessText: color.RGBA{R: 107, G: 196, B: 122, A: 255},
|
|
71
|
+
Warning: color.RGBA{R: 80, G: 60, B: 20, A: 255},
|
|
72
|
+
WarningBorder: color.RGBA{R: 150, G: 110, B: 30, A: 102},
|
|
73
|
+
WarningText: color.RGBA{R: 196, G: 164, B: 58, A: 255},
|
|
74
|
+
Error: color.RGBA{R: 80, G: 20, B: 20, A: 255},
|
|
75
|
+
ErrorBorder: color.RGBA{R: 180, G: 60, B: 60, A: 102},
|
|
76
|
+
ErrorText: color.RGBA{R: 196, G: 107, B: 107, A: 255},
|
|
77
|
+
Info: color.RGBA{R: 20, G: 53, B: 80, A: 255},
|
|
78
|
+
InfoBorder: color.RGBA{R: 60, G: 140, B: 200, A: 102},
|
|
79
|
+
InfoText: color.RGBA{R: 107, G: 168, B: 196, A: 255},
|
|
80
|
+
Border: color.RGBA{R: 255, G: 255, B: 255, A: 20},
|
|
81
|
+
BorderHover: color.RGBA{R: 255, G: 255, B: 255, A: 38},
|
|
82
|
+
BorderFocus: color.RGBA{R: 255, G: 255, B: 255, A: 64},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
7
85
|
|
|
8
|
-
|
|
9
|
-
|
|
86
|
+
type Typography struct {
|
|
87
|
+
FontFamily string
|
|
88
|
+
FontMono string
|
|
89
|
+
FontSizeXS float32
|
|
90
|
+
FontSizeSM float32
|
|
91
|
+
FontSizeBase float32
|
|
92
|
+
FontSizeMD float32
|
|
93
|
+
FontSizeLG float32
|
|
94
|
+
FontSizeXL float32
|
|
95
|
+
FontSize2XL float32
|
|
96
|
+
FontSize3XL float32
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func DefaultTypography() *Typography {
|
|
100
|
+
return &Typography{
|
|
101
|
+
FontFamily: "Outfit",
|
|
102
|
+
FontMono: "JetBrains Mono",
|
|
103
|
+
FontSizeXS: 11,
|
|
104
|
+
FontSizeSM: 12,
|
|
105
|
+
FontSizeBase: 13,
|
|
106
|
+
FontSizeMD: 14,
|
|
107
|
+
FontSizeLG: 16,
|
|
108
|
+
FontSizeXL: 20,
|
|
109
|
+
FontSize2XL: 28,
|
|
110
|
+
FontSize3XL: 36,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
10
113
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
ToastTypeWarning
|
|
15
|
-
ToastTypeInfo
|
|
16
|
-
)
|
|
114
|
+
type Spacing struct {
|
|
115
|
+
Space1, Space2, Space3, Space4, Space5, Space6, Space8, Space10, Space12 float32
|
|
116
|
+
}
|
|
17
117
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Title string
|
|
21
|
-
Message string
|
|
22
|
-
Type ToastType
|
|
23
|
-
Duration int
|
|
118
|
+
func DefaultSpacing() *Spacing {
|
|
119
|
+
return &Spacing{4, 8, 12, 16, 20, 24, 32, 40, 48}
|
|
24
120
|
}
|
|
25
121
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
t := &Toast{Message: message, Type: ToastTypeInfo, Duration: 4000}
|
|
29
|
-
for _, opt := range opts {
|
|
30
|
-
opt(t)
|
|
31
|
-
}
|
|
32
|
-
return t
|
|
122
|
+
type RadiusTokens struct {
|
|
123
|
+
SM, Default, LG, XL, Full float32
|
|
33
124
|
}
|
|
34
125
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return func(t *Toast) { t.Title = title }
|
|
126
|
+
func DefaultRadiusTokens() *RadiusTokens {
|
|
127
|
+
return &RadiusTokens{6, 10, 14, 20, 9999}
|
|
38
128
|
}
|
|
39
129
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return func(t *Toast) { t.Type = toastType }
|
|
130
|
+
type Shadow struct {
|
|
131
|
+
SM, Default, LG, Glow string
|
|
43
132
|
}
|
|
44
133
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
134
|
+
func DefaultShadow() *Shadow {
|
|
135
|
+
return &Shadow{
|
|
136
|
+
SM: "0 1px 2px rgba(0, 0, 0, 0.3)",
|
|
137
|
+
Default: "0 4px 12px rgba(0, 0, 0, 0.4)",
|
|
138
|
+
LG: "0 8px 24px rgba(0, 0, 0, 0.5)",
|
|
139
|
+
Glow: "0 0 20px rgba(107, 35, 35, 0.3)",
|
|
140
|
+
}
|
|
48
141
|
}
|
|
49
142
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return ToastShow(message, WithType(ToastTypeSuccess))
|
|
143
|
+
type Transition struct {
|
|
144
|
+
Fast, Default, Slow string
|
|
53
145
|
}
|
|
54
146
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return ToastShow(message, WithType(ToastTypeError))
|
|
147
|
+
func DefaultTransition() *Transition {
|
|
148
|
+
return &Transition{"0.1s ease", "0.15s ease", "0.25s ease"}
|
|
58
149
|
}
|
|
59
150
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return ToastShow(message, WithType(ToastTypeWarning))
|
|
151
|
+
type ZIndexTokens struct {
|
|
152
|
+
Dropdown, Sticky, Fixed, ModalBackdrop, Modal, Popover, Tooltip, Toast int
|
|
63
153
|
}
|
|
64
154
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return ToastShow(message, WithType(ToastTypeInfo))
|
|
155
|
+
func DefaultZIndexTokens() *ZIndexTokens {
|
|
156
|
+
return &ZIndexTokens{100, 200, 300, 400, 500, 600, 700, 800}
|
|
68
157
|
}
|
|
69
158
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
on bool
|
|
159
|
+
type LayoutTokens struct {
|
|
160
|
+
ContainerMax, SidebarWidth, HeaderHeight float32
|
|
73
161
|
}
|
|
74
162
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return &Toggle{}
|
|
163
|
+
func DefaultLayoutTokens() *LayoutTokens {
|
|
164
|
+
return &LayoutTokens{1200, 260, 56}
|
|
78
165
|
}
|
|
79
166
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// THEME
|
|
169
|
+
// =============================================================================
|
|
170
|
+
|
|
171
|
+
type Theme struct {
|
|
172
|
+
colors *Colors
|
|
173
|
+
spacing *Spacing
|
|
174
|
+
radius *RadiusTokens
|
|
83
175
|
}
|
|
84
176
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
177
|
+
func NewTheme() fyne.Theme {
|
|
178
|
+
return &Theme{
|
|
179
|
+
colors: DefaultColors(),
|
|
180
|
+
spacing: DefaultSpacing(),
|
|
181
|
+
radius: DefaultRadiusTokens(),
|
|
182
|
+
}
|
|
88
183
|
}
|
|
89
184
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
185
|
+
func (t *Theme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
|
186
|
+
switch name {
|
|
187
|
+
case theme.ColorNameBackground:
|
|
188
|
+
return t.colors.BG
|
|
189
|
+
case theme.ColorNameButton:
|
|
190
|
+
return t.colors.Surface2
|
|
191
|
+
case theme.ColorNameDisabledButton:
|
|
192
|
+
return t.colors.Surface3
|
|
193
|
+
case theme.ColorNameInputBackground:
|
|
194
|
+
return t.colors.Surface
|
|
195
|
+
case theme.ColorNameInputBorder:
|
|
196
|
+
return t.colors.Border
|
|
197
|
+
case theme.ColorNameDisabled:
|
|
198
|
+
return t.colors.TextDim
|
|
199
|
+
case theme.ColorNameForeground:
|
|
200
|
+
return t.colors.Text
|
|
201
|
+
case theme.ColorNamePlaceHolder:
|
|
202
|
+
return t.colors.TextMuted
|
|
203
|
+
case theme.ColorNamePrimary:
|
|
204
|
+
return t.colors.Accent
|
|
205
|
+
case theme.ColorNameHover:
|
|
206
|
+
return t.colors.Surface3
|
|
207
|
+
case theme.ColorNamePressed:
|
|
208
|
+
return t.colors.Surface4
|
|
209
|
+
case theme.ColorNameScrollBar:
|
|
210
|
+
return t.colors.Surface3
|
|
211
|
+
case theme.ColorNameShadow:
|
|
212
|
+
return color.RGBA{R: 0, G: 0, B: 0, A: 50}
|
|
213
|
+
case theme.ColorNameSuccess:
|
|
214
|
+
return t.colors.Success
|
|
215
|
+
case theme.ColorNameWarning:
|
|
216
|
+
return t.colors.Warning
|
|
217
|
+
case theme.ColorNameError:
|
|
218
|
+
return t.colors.Error
|
|
219
|
+
default:
|
|
220
|
+
return t.colors.Text
|
|
221
|
+
}
|
|
93
222
|
}
|
|
94
223
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
224
|
+
func (t *Theme) Font(style fyne.TextStyle) fyne.Resource {
|
|
225
|
+
return theme.DefaultTheme().Font(style)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func (t *Theme) Icon(name fyne.ThemeIconName) fyne.Resource {
|
|
229
|
+
return theme.DefaultTheme().Icon(name)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func (t *Theme) Size(name fyne.ThemeSizeName) float32 {
|
|
233
|
+
switch name {
|
|
234
|
+
case theme.SizeNamePadding:
|
|
235
|
+
return t.spacing.Space2
|
|
236
|
+
case theme.SizeNameInlinePadding:
|
|
237
|
+
return t.spacing.Space3
|
|
238
|
+
case theme.SizeNameScrollBar:
|
|
239
|
+
return 8
|
|
240
|
+
case theme.SizeNameScrollBarSmall:
|
|
241
|
+
return 4
|
|
242
|
+
case theme.SizeNameSeparatorThickness:
|
|
243
|
+
return 1
|
|
244
|
+
case theme.SizeNameText:
|
|
245
|
+
return t.spacing.Space4
|
|
246
|
+
case theme.SizeNameHeadingText:
|
|
247
|
+
return t.spacing.Space6
|
|
248
|
+
case theme.SizeNameSubHeadingText:
|
|
249
|
+
return t.spacing.Space5
|
|
250
|
+
case theme.SizeNameCaptionText:
|
|
251
|
+
return t.spacing.Space2
|
|
252
|
+
case theme.SizeNameInputBorder:
|
|
253
|
+
return 1
|
|
254
|
+
case theme.SizeNameInputRadius:
|
|
255
|
+
return t.radius.Default
|
|
256
|
+
case theme.SizeNameSelectionRadius:
|
|
257
|
+
return t.radius.SM
|
|
258
|
+
default:
|
|
259
|
+
return 0
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// =============================================================================
|
|
264
|
+
// BUTTONS
|
|
265
|
+
// =============================================================================
|
|
266
|
+
|
|
267
|
+
type ButtonVariant int
|
|
268
|
+
|
|
269
|
+
const (
|
|
270
|
+
ButtonDefault ButtonVariant = iota
|
|
271
|
+
ButtonPrimary
|
|
272
|
+
ButtonGhost
|
|
273
|
+
ButtonOutline
|
|
274
|
+
ButtonDanger
|
|
275
|
+
ButtonSuccess
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
func NewButton(text string, variant ButtonVariant, onTap func()) *widget.Button {
|
|
279
|
+
btn := widget.NewButton(text, onTap)
|
|
280
|
+
switch variant {
|
|
281
|
+
case ButtonPrimary:
|
|
282
|
+
btn.Importance = widget.HighImportance
|
|
283
|
+
case ButtonDanger:
|
|
284
|
+
btn.Importance = widget.DangerImportance
|
|
285
|
+
case ButtonSuccess:
|
|
286
|
+
btn.Importance = widget.HighImportance
|
|
287
|
+
case ButtonGhost, ButtonOutline:
|
|
288
|
+
btn.Importance = widget.LowImportance
|
|
289
|
+
default:
|
|
290
|
+
btn.Importance = widget.MediumImportance
|
|
291
|
+
}
|
|
292
|
+
return btn
|
|
98
293
|
}
|
|
99
294
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return &Modal{}
|
|
295
|
+
func NewButtonGroup(buttons ...*widget.Button) *fyne.Container {
|
|
296
|
+
return container.NewHBox(buttons...)
|
|
103
297
|
}
|
|
104
298
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
299
|
+
// =============================================================================
|
|
300
|
+
// CARD
|
|
301
|
+
// =============================================================================
|
|
302
|
+
|
|
303
|
+
// CardOption is a function that configures a card.
|
|
304
|
+
type CardOption func(*CardConfig)
|
|
305
|
+
|
|
306
|
+
// CardConfig holds card configuration.
|
|
307
|
+
type CardConfig struct {
|
|
308
|
+
Title string
|
|
309
|
+
Subtitle string
|
|
310
|
+
Icon fyne.CanvasObject
|
|
311
|
+
Footer fyne.CanvasObject
|
|
312
|
+
Clickable bool
|
|
108
313
|
}
|
|
109
314
|
|
|
110
|
-
//
|
|
111
|
-
func (
|
|
112
|
-
|
|
315
|
+
// WithCardTitle sets the card title.
|
|
316
|
+
func WithCardTitle(title string) CardOption {
|
|
317
|
+
return func(c *CardConfig) { c.Title = title }
|
|
113
318
|
}
|
|
114
319
|
|
|
115
|
-
//
|
|
116
|
-
func (
|
|
117
|
-
return
|
|
320
|
+
// WithCardSubtitle sets the card subtitle.
|
|
321
|
+
func WithCardSubtitle(subtitle string) CardOption {
|
|
322
|
+
return func(c *CardConfig) { c.Subtitle = subtitle }
|
|
118
323
|
}
|
|
119
324
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
325
|
+
// WithCardIcon sets the card icon.
|
|
326
|
+
func WithCardIcon(icon fyne.CanvasObject) CardOption {
|
|
327
|
+
return func(c *CardConfig) { c.Icon = icon }
|
|
123
328
|
}
|
|
124
329
|
|
|
125
|
-
//
|
|
126
|
-
func
|
|
127
|
-
return
|
|
330
|
+
// WithCardFooter sets the card footer.
|
|
331
|
+
func WithCardFooter(footer fyne.CanvasObject) CardOption {
|
|
332
|
+
return func(c *CardConfig) { c.Footer = footer }
|
|
128
333
|
}
|
|
129
334
|
|
|
130
|
-
//
|
|
131
|
-
func (
|
|
132
|
-
|
|
335
|
+
// WithCardClickable makes the card clickable.
|
|
336
|
+
func WithCardClickable(clickable bool) CardOption {
|
|
337
|
+
return func(c *CardConfig) { c.Clickable = clickable }
|
|
133
338
|
}
|
|
134
339
|
|
|
135
|
-
//
|
|
136
|
-
func (
|
|
137
|
-
|
|
340
|
+
// NewCard creates a card widget with optional icon, title, subtitle, and footer.
|
|
341
|
+
func NewCard(content fyne.CanvasObject, options ...CardOption) *fyne.Container {
|
|
342
|
+
config := &CardConfig{}
|
|
343
|
+
for _, opt := range options {
|
|
344
|
+
opt(config)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
c := DefaultColors()
|
|
348
|
+
|
|
349
|
+
var headerObjects []fyne.CanvasObject
|
|
350
|
+
if config.Icon != nil {
|
|
351
|
+
headerObjects = append(headerObjects, config.Icon)
|
|
352
|
+
}
|
|
353
|
+
if config.Title != "" {
|
|
354
|
+
titleText := canvas.NewText(config.Title, c.Text)
|
|
355
|
+
titleText.TextSize = 14
|
|
356
|
+
titleText.TextStyle = fyne.TextStyle{Bold: true}
|
|
357
|
+
headerObjects = append(headerObjects, titleText)
|
|
358
|
+
}
|
|
359
|
+
if config.Subtitle != "" {
|
|
360
|
+
subtitleText := canvas.NewText(config.Subtitle, c.TextMuted)
|
|
361
|
+
subtitleText.TextSize = 12
|
|
362
|
+
headerObjects = append(headerObjects, subtitleText)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
var header *fyne.Container
|
|
366
|
+
if len(headerObjects) > 0 {
|
|
367
|
+
header = container.NewVBox(headerObjects...)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
var body fyne.CanvasObject
|
|
371
|
+
if header != nil {
|
|
372
|
+
body = container.NewVBox(header, content)
|
|
373
|
+
} else {
|
|
374
|
+
body = content
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
var finalContent *fyne.Container
|
|
378
|
+
if config.Footer != nil {
|
|
379
|
+
finalContent = container.NewVBox(body, canvas.NewRectangle(c.Border), config.Footer)
|
|
380
|
+
} else {
|
|
381
|
+
if c, ok := body.(*fyne.Container); ok {
|
|
382
|
+
finalContent = c
|
|
383
|
+
} else {
|
|
384
|
+
finalContent = container.NewMax(body)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
bg := canvas.NewRectangle(c.Surface)
|
|
389
|
+
bg.StrokeColor = c.Border
|
|
390
|
+
bg.StrokeWidth = 1
|
|
391
|
+
bg.CornerRadius = 14
|
|
392
|
+
|
|
393
|
+
return container.NewStack(bg, container.NewPadded(finalContent))
|
|
138
394
|
}
|
|
139
395
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
396
|
+
// =============================================================================
|
|
397
|
+
// INPUTS
|
|
398
|
+
// =============================================================================
|
|
399
|
+
|
|
400
|
+
func NewInput(placeholder string) *widget.Entry {
|
|
401
|
+
entry := widget.NewEntry()
|
|
402
|
+
entry.SetPlaceHolder(placeholder)
|
|
403
|
+
return entry
|
|
143
404
|
}
|
|
144
405
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
406
|
+
func NewTextarea(placeholder string) *widget.Entry {
|
|
407
|
+
entry := widget.NewMultiLineEntry()
|
|
408
|
+
entry.SetPlaceHolder(placeholder)
|
|
409
|
+
return entry
|
|
148
410
|
}
|
|
149
411
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
412
|
+
func NewPassword(placeholder string) *widget.Entry {
|
|
413
|
+
entry := widget.NewPasswordEntry()
|
|
414
|
+
entry.SetPlaceHolder(placeholder)
|
|
415
|
+
return entry
|
|
153
416
|
}
|
|
154
417
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
418
|
+
// =============================================================================
|
|
419
|
+
// FORM ELEMENTS
|
|
420
|
+
// =============================================================================
|
|
421
|
+
|
|
422
|
+
func NewCheckbox(label string, onChanged func(bool)) *widget.Check {
|
|
423
|
+
return widget.NewCheck(label, onChanged)
|
|
158
424
|
}
|
|
159
425
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
t.activeIndex = index
|
|
426
|
+
func NewRadio(options []string, onChanged func(string)) *widget.RadioGroup {
|
|
427
|
+
return widget.NewRadioGroup(options, onChanged)
|
|
163
428
|
}
|
|
164
429
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return t.activeIndex
|
|
430
|
+
func NewSelect(options []string, onChanged func(string)) *widget.Select {
|
|
431
|
+
return widget.NewSelect(options, onChanged)
|
|
168
432
|
}
|
|
169
433
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
openIndices map[int]bool
|
|
434
|
+
func NewSlider(min, max float64) *widget.Slider {
|
|
435
|
+
return widget.NewSlider(min, max)
|
|
173
436
|
}
|
|
174
437
|
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
438
|
+
// =============================================================================
|
|
439
|
+
// PROGRESS & LOADING
|
|
440
|
+
// =============================================================================
|
|
441
|
+
|
|
442
|
+
func NewProgress() *widget.ProgressBar {
|
|
443
|
+
return widget.NewProgressBar()
|
|
178
444
|
}
|
|
179
445
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
a.openIndices[index] = !a.openIndices[index]
|
|
446
|
+
func NewProgressInfinite() *widget.ProgressBarInfinite {
|
|
447
|
+
return widget.NewProgressBarInfinite()
|
|
183
448
|
}
|
|
184
449
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
450
|
+
// =============================================================================
|
|
451
|
+
// TOGGLE
|
|
452
|
+
// =============================================================================
|
|
453
|
+
|
|
454
|
+
type Toggle struct {
|
|
455
|
+
widget.BaseWidget
|
|
456
|
+
on bool
|
|
457
|
+
onToggle func(bool)
|
|
188
458
|
}
|
|
189
459
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
460
|
+
func NewToggle(onToggle func(bool)) *Toggle {
|
|
461
|
+
t := &Toggle{onToggle: onToggle}
|
|
462
|
+
t.ExtendBaseWidget(t)
|
|
463
|
+
return t
|
|
193
464
|
}
|
|
194
465
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
466
|
+
func (t *Toggle) Toggle() {
|
|
467
|
+
t.on = !t.on
|
|
468
|
+
if t.onToggle != nil {
|
|
469
|
+
t.onToggle(t.on)
|
|
199
470
|
}
|
|
471
|
+
t.Refresh()
|
|
200
472
|
}
|
|
201
473
|
|
|
202
|
-
|
|
203
|
-
func (
|
|
204
|
-
|
|
474
|
+
func (t *Toggle) IsOn() bool { return t.on }
|
|
475
|
+
func (t *Toggle) SetOn(value bool) { t.on = value; t.Refresh() }
|
|
476
|
+
|
|
477
|
+
// =============================================================================
|
|
478
|
+
// MODAL
|
|
479
|
+
// =============================================================================
|
|
480
|
+
|
|
481
|
+
type Modal struct {
|
|
482
|
+
widget.BaseWidget
|
|
483
|
+
content fyne.CanvasObject
|
|
484
|
+
open bool
|
|
485
|
+
window fyne.Window
|
|
205
486
|
}
|
|
206
487
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
488
|
+
func NewModal(content fyne.CanvasObject) *Modal {
|
|
489
|
+
m := &Modal{content: content}
|
|
490
|
+
m.ExtendBaseWidget(m)
|
|
491
|
+
return m
|
|
210
492
|
}
|
|
211
493
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
494
|
+
func (m *Modal) Open(window fyne.Window) {
|
|
495
|
+
m.window = window
|
|
496
|
+
m.open = true
|
|
497
|
+
m.Show()
|
|
216
498
|
}
|
|
217
499
|
|
|
218
|
-
|
|
219
|
-
func
|
|
220
|
-
|
|
221
|
-
|
|
500
|
+
func (m *Modal) Close() { m.open = false; m.Hide() }
|
|
501
|
+
func (m *Modal) IsOpen() bool { return m.open }
|
|
502
|
+
func (m *Modal) Show() {
|
|
503
|
+
if m.window != nil {
|
|
504
|
+
popup := widget.NewModalPopUp(m.content, m.window.Canvas())
|
|
505
|
+
popup.Show()
|
|
222
506
|
}
|
|
223
|
-
|
|
224
|
-
|
|
507
|
+
}
|
|
508
|
+
func (m *Modal) Hide() {}
|
|
509
|
+
|
|
510
|
+
// =============================================================================
|
|
511
|
+
// DROPDOWN
|
|
512
|
+
// =============================================================================
|
|
513
|
+
|
|
514
|
+
// Dropdown creates a dropdown selection widget using Fyne's Select widget.
|
|
515
|
+
func NewDropdown(items []string, onSelect func(string)) *widget.Select {
|
|
516
|
+
s := widget.NewSelect(items, onSelect)
|
|
517
|
+
return s
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// =============================================================================
|
|
521
|
+
// TABS
|
|
522
|
+
// =============================================================================
|
|
523
|
+
|
|
524
|
+
func NewTabs(items ...*widget.TabItem) *container.AppTabs {
|
|
525
|
+
return container.NewAppTabs(items...)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// =============================================================================
|
|
529
|
+
// ACCORDION
|
|
530
|
+
// =============================================================================
|
|
531
|
+
|
|
532
|
+
type AccordionItem struct {
|
|
533
|
+
Title string
|
|
534
|
+
Content fyne.CanvasObject
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
type Accordion struct {
|
|
538
|
+
widget.BaseWidget
|
|
539
|
+
items []AccordionItem
|
|
540
|
+
openIndices map[int]bool
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
func NewAccordion(items ...AccordionItem) *Accordion {
|
|
544
|
+
a := &Accordion{items: items, openIndices: make(map[int]bool)}
|
|
545
|
+
a.ExtendBaseWidget(a)
|
|
546
|
+
return a
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
func (a *Accordion) Toggle(index int) { a.openIndices[index] = !a.openIndices[index]; a.Refresh() }
|
|
550
|
+
func (a *Accordion) Open(index int) { a.openIndices[index] = true; a.Refresh() }
|
|
551
|
+
func (a *Accordion) Close(index int) { delete(a.openIndices, index); a.Refresh() }
|
|
552
|
+
func (a *Accordion) OpenAll() {
|
|
553
|
+
for i := range a.items {
|
|
554
|
+
a.openIndices[i] = true
|
|
225
555
|
}
|
|
226
|
-
|
|
556
|
+
a.Refresh()
|
|
557
|
+
}
|
|
558
|
+
func (a *Accordion) CloseAll() { a.openIndices = make(map[int]bool); a.Refresh() }
|
|
559
|
+
func (a *Accordion) IsOpen(index int) bool { return a.openIndices[index] }
|
|
560
|
+
|
|
561
|
+
// =============================================================================
|
|
562
|
+
// PAGINATION
|
|
563
|
+
// =============================================================================
|
|
564
|
+
|
|
565
|
+
type Pagination struct {
|
|
566
|
+
widget.BaseWidget
|
|
567
|
+
total int
|
|
568
|
+
current int
|
|
569
|
+
onChange func(int)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
func NewPagination(total, current int, onChange func(int)) *Pagination {
|
|
573
|
+
p := &Pagination{total: total, current: current, onChange: onChange}
|
|
574
|
+
p.ExtendBaseWidget(p)
|
|
575
|
+
return p
|
|
227
576
|
}
|
|
228
577
|
|
|
229
|
-
// GoTo navigates to a specific page
|
|
230
578
|
func (p *Pagination) GoTo(page int) {
|
|
231
579
|
if page >= 1 && page <= p.total {
|
|
232
580
|
p.current = page
|
|
581
|
+
if p.onChange != nil {
|
|
582
|
+
p.onChange(page)
|
|
583
|
+
}
|
|
584
|
+
p.Refresh()
|
|
233
585
|
}
|
|
234
586
|
}
|
|
235
|
-
|
|
236
|
-
// Next goes to next page
|
|
237
587
|
func (p *Pagination) Next() {
|
|
238
588
|
if p.current < p.total {
|
|
239
|
-
p.current
|
|
589
|
+
p.GoTo(p.current + 1)
|
|
240
590
|
}
|
|
241
591
|
}
|
|
242
|
-
|
|
243
|
-
// Prev goes to previous page
|
|
244
592
|
func (p *Pagination) Prev() {
|
|
245
593
|
if p.current > 1 {
|
|
246
|
-
p.current
|
|
594
|
+
p.GoTo(p.current - 1)
|
|
247
595
|
}
|
|
248
596
|
}
|
|
597
|
+
func (p *Pagination) Current() int { return p.current }
|
|
598
|
+
func (p *Pagination) Total() int { return p.total }
|
|
599
|
+
|
|
600
|
+
// =============================================================================
|
|
601
|
+
// TOAST
|
|
602
|
+
// =============================================================================
|
|
603
|
+
|
|
604
|
+
type ToastType int
|
|
605
|
+
|
|
606
|
+
const (
|
|
607
|
+
ToastSuccess ToastType = iota
|
|
608
|
+
ToastError
|
|
609
|
+
ToastWarning
|
|
610
|
+
ToastInfo
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
func ShowToast(window fyne.Window, message string, toastType ToastType) {
|
|
614
|
+
c := DefaultColors()
|
|
615
|
+
var bgColor color.Color
|
|
616
|
+
var prefix string
|
|
617
|
+
switch toastType {
|
|
618
|
+
case ToastSuccess:
|
|
619
|
+
bgColor = c.Success
|
|
620
|
+
prefix = "✓ "
|
|
621
|
+
case ToastError:
|
|
622
|
+
bgColor = c.Error
|
|
623
|
+
prefix = "✕ "
|
|
624
|
+
case ToastWarning:
|
|
625
|
+
bgColor = c.Warning
|
|
626
|
+
prefix = "⚠ "
|
|
627
|
+
default:
|
|
628
|
+
bgColor = c.Info
|
|
629
|
+
prefix = "ℹ "
|
|
630
|
+
}
|
|
631
|
+
label := canvas.NewText(prefix+message, c.Text)
|
|
632
|
+
label.TextSize = 13
|
|
633
|
+
bg := canvas.NewRectangle(bgColor)
|
|
634
|
+
bg.CornerRadius = 10
|
|
635
|
+
cont := container.NewStack(bg, container.NewPadded(label))
|
|
636
|
+
popover := widget.NewPopUp(cont, window.Canvas())
|
|
637
|
+
popover.Move(fyne.NewPos(window.Canvas().Size().Width-320, window.Canvas().Size().Height-60))
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// =============================================================================
|
|
641
|
+
// COMMAND PALETTE
|
|
642
|
+
// =============================================================================
|
|
643
|
+
|
|
644
|
+
type CommandItem struct {
|
|
645
|
+
Title, Subtitle, Kbd string
|
|
646
|
+
Action func()
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// CommandPalette creates a modal command palette with search and filtering.
|
|
650
|
+
func NewCommandPalette(window fyne.Window, items []CommandItem) *fyne.Container {
|
|
651
|
+
c := DefaultColors()
|
|
652
|
+
searchEntry := widget.NewEntry()
|
|
653
|
+
searchEntry.SetPlaceHolder("Type to search commands...")
|
|
654
|
+
|
|
655
|
+
list := widget.NewList(
|
|
656
|
+
func() int { return len(items) },
|
|
657
|
+
func() fyne.CanvasObject {
|
|
658
|
+
return container.NewVBox(
|
|
659
|
+
widget.NewLabel("Command"),
|
|
660
|
+
widget.NewLabel("Description"),
|
|
661
|
+
)
|
|
662
|
+
},
|
|
663
|
+
func(id widget.ListItemID, item fyne.CanvasObject) {
|
|
664
|
+
if id < len(items) {
|
|
665
|
+
vbox := item.(*fyne.Container)
|
|
666
|
+
vbox.Objects[0].(*widget.Label).SetText(items[id].Title)
|
|
667
|
+
vbox.Objects[1].(*widget.Label).SetText(items[id].Subtitle)
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
searchEntry.OnChanged = func(query string) {
|
|
673
|
+
// Filter logic can be added here
|
|
674
|
+
list.Refresh()
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
list.OnSelected = func(id widget.ListItemID) {
|
|
678
|
+
if id < len(items) && items[id].Action != nil {
|
|
679
|
+
items[id].Action()
|
|
680
|
+
window.Hide()
|
|
681
|
+
}
|
|
682
|
+
}
|
|
249
683
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
684
|
+
content := container.NewVBox(
|
|
685
|
+
searchEntry,
|
|
686
|
+
container.NewScroll(list),
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
bg := canvas.NewRectangle(c.Surface)
|
|
690
|
+
bg.CornerRadius = 14
|
|
691
|
+
|
|
692
|
+
return container.NewStack(bg, container.NewPadded(content))
|
|
253
693
|
}
|
|
254
694
|
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
695
|
+
// =============================================================================
|
|
696
|
+
// SEARCH
|
|
697
|
+
// =============================================================================
|
|
698
|
+
|
|
699
|
+
type SearchItem struct {
|
|
700
|
+
Title, Subtitle string
|
|
701
|
+
Action func()
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Search creates a search widget with input and results list.
|
|
705
|
+
func NewSearch(items []SearchItem, onSelect func(SearchItem)) *fyne.Container {
|
|
706
|
+
c := DefaultColors()
|
|
707
|
+
searchEntry := widget.NewEntry()
|
|
708
|
+
searchEntry.SetPlaceHolder("Search...")
|
|
709
|
+
|
|
710
|
+
results := make([]SearchItem, 0)
|
|
711
|
+
resultsList := widget.NewList(
|
|
712
|
+
func() int { return len(results) },
|
|
713
|
+
func() fyne.CanvasObject {
|
|
714
|
+
return container.NewVBox(
|
|
715
|
+
widget.NewLabel("Title"),
|
|
716
|
+
widget.NewLabel("Subtitle"),
|
|
717
|
+
)
|
|
718
|
+
},
|
|
719
|
+
func(id widget.ListItemID, item fyne.CanvasObject) {
|
|
720
|
+
if id < len(results) {
|
|
721
|
+
vbox := item.(*fyne.Container)
|
|
722
|
+
vbox.Objects[0].(*widget.Label).SetText(results[id].Title)
|
|
723
|
+
vbox.Objects[1].(*widget.Label).SetText(results[id].Subtitle)
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
resultsList.OnSelected = func(id widget.ListItemID) {
|
|
729
|
+
if id < len(results) && onSelect != nil {
|
|
730
|
+
onSelect(results[id])
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
searchEntry.OnChanged = func(query string) {
|
|
735
|
+
results = make([]SearchItem, 0)
|
|
736
|
+
if query == "" {
|
|
737
|
+
resultsList.Refresh()
|
|
738
|
+
return
|
|
739
|
+
}
|
|
740
|
+
for _, item := range items {
|
|
741
|
+
// Simple case-insensitive search
|
|
742
|
+
if containsIgnoreCase(item.Title, query) || containsIgnoreCase(item.Subtitle, query) {
|
|
743
|
+
results = append(results, item)
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
resultsList.Refresh()
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
content := container.NewVBox(
|
|
750
|
+
searchEntry,
|
|
751
|
+
container.NewScroll(resultsList),
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
return content
|
|
258
755
|
}
|
|
259
756
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
Title string
|
|
263
|
-
Kbd string
|
|
264
|
-
Action func()
|
|
757
|
+
func containsIgnoreCase(s, substr string) bool {
|
|
758
|
+
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
|
265
759
|
}
|
|
266
760
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
761
|
+
// =============================================================================
|
|
762
|
+
// TABLE
|
|
763
|
+
// =============================================================================
|
|
764
|
+
|
|
765
|
+
func NewTable(rows, cols int, cellFunc func(row, col int) fyne.CanvasObject) *widget.Table {
|
|
766
|
+
return widget.NewTable(
|
|
767
|
+
func() (int, int) { return rows, cols },
|
|
768
|
+
func() fyne.CanvasObject { return widget.NewLabel("") },
|
|
769
|
+
func(id widget.TableCellID, cell fyne.CanvasObject) {
|
|
770
|
+
if obj := cellFunc(id.Row, id.Col); obj != nil {
|
|
771
|
+
cell.(*widget.Label).SetText(obj.(*widget.Label).Text)
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
)
|
|
271
775
|
}
|
|
272
776
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
777
|
+
// =============================================================================
|
|
778
|
+
// LIST
|
|
779
|
+
// =============================================================================
|
|
780
|
+
|
|
781
|
+
type ListItem struct {
|
|
782
|
+
Title, Subtitle string
|
|
276
783
|
}
|
|
277
784
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
785
|
+
func NewList(items []ListItem) *widget.List {
|
|
786
|
+
return widget.NewList(
|
|
787
|
+
func() int { return len(items) },
|
|
788
|
+
func() fyne.CanvasObject { return widget.NewLabel("") },
|
|
789
|
+
func(id widget.ListItemID, item fyne.CanvasObject) {
|
|
790
|
+
item.(*widget.Label).SetText(items[id].Title)
|
|
791
|
+
},
|
|
792
|
+
)
|
|
281
793
|
}
|
|
282
794
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
795
|
+
// =============================================================================
|
|
796
|
+
// LAYOUT HELPERS
|
|
797
|
+
// =============================================================================
|
|
798
|
+
|
|
799
|
+
func NewContainer(content fyne.CanvasObject) fyne.CanvasObject {
|
|
800
|
+
return container.NewMax(content)
|
|
286
801
|
}
|
|
287
802
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
c.open = !c.open
|
|
803
|
+
func NewFlex(objects ...fyne.CanvasObject) fyne.CanvasObject {
|
|
804
|
+
return container.NewHBox(objects...)
|
|
291
805
|
}
|
|
292
806
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return c.open
|
|
807
|
+
func NewStack(objects ...fyne.CanvasObject) fyne.CanvasObject {
|
|
808
|
+
return container.NewVBox(objects...)
|
|
296
809
|
}
|
|
297
810
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
c.items = items
|
|
811
|
+
func NewGrid(cols int, objects ...fyne.CanvasObject) fyne.CanvasObject {
|
|
812
|
+
return container.NewGridWithColumns(cols, objects...)
|
|
301
813
|
}
|
|
302
814
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return c.items
|
|
815
|
+
func NewForm(items ...*widget.FormItem) *widget.Form {
|
|
816
|
+
return widget.NewForm(items...)
|
|
306
817
|
}
|
|
307
818
|
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
819
|
+
// =============================================================================
|
|
820
|
+
// NAV
|
|
821
|
+
// =============================================================================
|
|
822
|
+
|
|
823
|
+
// Nav creates a navigation sidebar widget.
|
|
824
|
+
func NewNav(items []string, active int, onSelect func(int)) *widget.List {
|
|
825
|
+
list := widget.NewList(
|
|
826
|
+
func() int { return len(items) },
|
|
827
|
+
func() fyne.CanvasObject {
|
|
828
|
+
return widget.NewLabel("Nav Item")
|
|
829
|
+
},
|
|
830
|
+
func(id widget.ListItemID, item fyne.CanvasObject) {
|
|
831
|
+
if id < len(items) {
|
|
832
|
+
item.(*widget.Label).SetText(items[id])
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
)
|
|
836
|
+
list.OnSelected = func(id widget.ListItemID) {
|
|
837
|
+
if onSelect != nil {
|
|
838
|
+
onSelect(id)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if active >= 0 && active < len(items) {
|
|
842
|
+
list.Select(active)
|
|
313
843
|
}
|
|
844
|
+
return list
|
|
314
845
|
}
|
|
315
846
|
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
Subtitle string
|
|
320
|
-
Action func()
|
|
321
|
-
}
|
|
847
|
+
// =============================================================================
|
|
848
|
+
// STAT
|
|
849
|
+
// =============================================================================
|
|
322
850
|
|
|
323
|
-
|
|
324
|
-
type Search struct {
|
|
325
|
-
open bool
|
|
326
|
-
items []SearchItem
|
|
327
|
-
}
|
|
851
|
+
type StatDeltaType int
|
|
328
852
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
853
|
+
const (
|
|
854
|
+
StatDeltaUp StatDeltaType = iota
|
|
855
|
+
StatDeltaDown
|
|
856
|
+
)
|
|
333
857
|
|
|
334
|
-
//
|
|
335
|
-
func (
|
|
336
|
-
|
|
337
|
-
|
|
858
|
+
// Stat creates a stat/metric display widget with value, label, and optional delta.
|
|
859
|
+
func NewStat(value string, label string, delta string, deltaType StatDeltaType) *fyne.Container {
|
|
860
|
+
c := DefaultColors()
|
|
861
|
+
|
|
862
|
+
valueLabel := canvas.NewText(value, c.Text)
|
|
863
|
+
valueLabel.TextSize = 28
|
|
864
|
+
valueLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
865
|
+
|
|
866
|
+
labelWidget := canvas.NewText(label, c.TextMuted)
|
|
867
|
+
labelWidget.TextSize = 11
|
|
868
|
+
|
|
869
|
+
var deltaWidget *canvas.Text
|
|
870
|
+
if delta != "" {
|
|
871
|
+
deltaColor := c.SuccessText
|
|
872
|
+
if deltaType == StatDeltaDown {
|
|
873
|
+
deltaColor = c.ErrorText
|
|
874
|
+
}
|
|
875
|
+
prefix := "↑ "
|
|
876
|
+
if deltaType == StatDeltaDown {
|
|
877
|
+
prefix = "↓ "
|
|
878
|
+
}
|
|
879
|
+
deltaWidget = canvas.NewText(prefix+delta, deltaColor)
|
|
880
|
+
deltaWidget.TextSize = 11
|
|
881
|
+
}
|
|
338
882
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
883
|
+
content := container.NewVBox(
|
|
884
|
+
valueLabel,
|
|
885
|
+
labelWidget,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
if deltaWidget != nil {
|
|
889
|
+
content = container.NewVBox(
|
|
890
|
+
valueLabel,
|
|
891
|
+
labelWidget,
|
|
892
|
+
deltaWidget,
|
|
893
|
+
)
|
|
344
894
|
}
|
|
345
|
-
return results
|
|
346
|
-
}
|
|
347
895
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
896
|
+
bg := canvas.NewRectangle(c.Surface)
|
|
897
|
+
bg.StrokeColor = c.Border
|
|
898
|
+
bg.StrokeWidth = 1
|
|
899
|
+
bg.CornerRadius = 10
|
|
352
900
|
|
|
353
|
-
|
|
354
|
-
func (s *Search) Close() {
|
|
355
|
-
s.open = false
|
|
901
|
+
return container.NewStack(bg, container.NewPadded(content))
|
|
356
902
|
}
|
|
357
903
|
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
904
|
+
// =============================================================================
|
|
905
|
+
// APPLICATION
|
|
906
|
+
// =============================================================================
|
|
907
|
+
|
|
908
|
+
type App struct {
|
|
909
|
+
fyne.App
|
|
910
|
+
Theme fyne.Theme
|
|
361
911
|
}
|
|
362
912
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
913
|
+
func NewApp() *App {
|
|
914
|
+
a := app.New()
|
|
915
|
+
t := NewTheme()
|
|
916
|
+
a.Settings().SetTheme(t)
|
|
917
|
+
return &App{App: a, Theme: t}
|
|
366
918
|
}
|
|
367
919
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if index >= 0 && index < len(s.items) && s.items[index].Action != nil {
|
|
371
|
-
s.items[index].Action()
|
|
372
|
-
s.Close()
|
|
373
|
-
}
|
|
920
|
+
func (a *App) NewWindow(title string) fyne.Window {
|
|
921
|
+
return a.App.NewWindow(title)
|
|
374
922
|
}
|
|
375
923
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
fmt.Println("CronixUI", Version, "initialized")
|
|
924
|
+
func Init() fyne.Theme {
|
|
925
|
+
return NewTheme()
|
|
379
926
|
}
|