1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
3 | |
4 | import { StateLayer } from "components.slint" ; |
5 | import { MaterialFontSettings, MaterialPalette, Elevation } from "styling.slint" ; |
6 | |
7 | component MaterialButtonBase { |
8 | in property <string> text; |
9 | in property <image> icon; |
10 | in property <length> border-radius <=> state-layer.border-radius; |
11 | in property <bool> checkable; |
12 | in property <brush> text-color; |
13 | in property <float> text-opacity; |
14 | in property <bool> colorize-icon; |
15 | in property <bool> enabled <=> state-layer.enabled; |
16 | in property <length> layout-padding-left; |
17 | in property <length> layout-padding-right; |
18 | out property <bool> has-focus <=> state-layer.has-focus; |
19 | out property <bool> pressed <=> state-layer.pressed; |
20 | in-out property <bool> checked; |
21 | |
22 | callback clicked <=> state-layer.clicked; |
23 | |
24 | min-width: layout.min-width; |
25 | min-height: layout.min-height; |
26 | forward-focus: state-layer; |
27 | |
28 | state-layer := StateLayer { |
29 | width: 100%; |
30 | height: 100%; |
31 | has-ripple: true; |
32 | background: MaterialPalette.foreground-alt; |
33 | ripple-color: MaterialPalette.secondary-ripple; |
34 | checked-background: MaterialPalette.accent-background; |
35 | focusable: true; |
36 | } |
37 | |
38 | layout := HorizontalLayout { |
39 | spacing: 8px; |
40 | padding-left: root.layout-padding-left; |
41 | padding-right: root.layout-padding-right; |
42 | |
43 | if root.icon.width > 0 && root.icon.height > 0: Image { |
44 | source <=> root.icon; |
45 | width: 24px; |
46 | opacity: root.text-opacity; |
47 | colorize: root.colorize-icon ? root.text-color : transparent; |
48 | } |
49 | |
50 | if root.text != "" : Text { |
51 | text: root.text; |
52 | color: root.text-color; |
53 | opacity: root.text-opacity; |
54 | vertical-alignment: center; |
55 | horizontal-alignment: center; |
56 | font-weight: MaterialFontSettings.label-large.font-weight; |
57 | accessible-role: none; |
58 | |
59 | animate color { |
60 | duration: 250ms; |
61 | easing: ease; |
62 | } |
63 | } |
64 | } |
65 | } |
66 | |
67 | // Default button widget with Material Design Filled Button look and feel. |
68 | export component Button { |
69 | in property <string> text <=> base.text; |
70 | in property <bool> enabled <=> base.enabled; |
71 | in property <bool> checkable; |
72 | in property <image> icon <=> base.icon; |
73 | in property <bool> primary; |
74 | in property <bool> colorize-icon <=> base.colorize-icon; |
75 | out property <bool> has-focus: base.has-focus; |
76 | out property <bool> pressed: self.enabled && base.pressed; |
77 | in-out property <bool> checked; |
78 | |
79 | callback clicked; |
80 | |
81 | min-height: max(40px, layout.min-height); |
82 | min-width: max(40px, layout.min-width); |
83 | forward-focus: base; |
84 | |
85 | accessible-role: button; |
86 | accessible-enabled: root.enabled; |
87 | accessible-checkable: root.checkable; |
88 | accessible-checked: root.checked; |
89 | accessible-label: root.text; |
90 | accessible-action-default => { |
91 | base.clicked(); |
92 | } |
93 | |
94 | states [ |
95 | disabled when !root.enabled: { |
96 | background.background: MaterialPalette.foreground-alt; |
97 | background.opacity: 0.12; |
98 | base.text-opacity: 0.38; |
99 | base.text-color: MaterialPalette.control-foreground; |
100 | } |
101 | checked when root.checked: { |
102 | base.text-color: MaterialPalette.accent-foreground; |
103 | } |
104 | ] |
105 | |
106 | background := Rectangle { |
107 | width: 100%; |
108 | height: 100%; |
109 | border-radius: 20px; |
110 | background: root.primary ? MaterialPalette.accent-background : MaterialPalette.control-background; |
111 | drop-shadow-color: transparent; |
112 | drop-shadow-blur: Elevation.level0; |
113 | drop-shadow-offset-y: 1px; |
114 | } |
115 | |
116 | layout := HorizontalLayout { |
117 | base := MaterialButtonBase { |
118 | layout-padding-left: 24px; |
119 | layout-padding-right: 24px; |
120 | border-radius: background.border-radius; |
121 | text-color: root.primary ? MaterialPalette.accent-foreground : MaterialPalette.control-foreground; |
122 | text-opacity: 1.0; |
123 | |
124 | clicked => { |
125 | if root.checkable { |
126 | root.checked = !root.checked; |
127 | } |
128 | root.clicked(); |
129 | } |
130 | } |
131 | } |
132 | } |
133 | |
134 | export component TextButton { |
135 | in property <string> text <=> base.text; |
136 | in property <bool> enabled <=> base.enabled; |
137 | in property <image> icon <=> base.icon; |
138 | in property <bool> colorize-icon <=> base.colorize-icon; |
139 | out property <bool> has-focus: base.has-focus; |
140 | out property <bool> pressed: self.enabled && base.pressed; |
141 | |
142 | callback clicked <=> base.clicked; |
143 | |
144 | min-height: max(40px, base.min-height); |
145 | min-width: max(40px, base.min-width); |
146 | forward-focus: base; |
147 | |
148 | accessible-role: button; |
149 | accessible-label: root.text; |
150 | accessible-action-default => { |
151 | clicked(); |
152 | } |
153 | |
154 | base := MaterialButtonBase { |
155 | layout-padding-left: 12px; |
156 | layout-padding-right: 12px; |
157 | height: 100%; |
158 | border-radius: 20px; |
159 | text-color: MaterialPalette.accent-background; |
160 | text-opacity: 1.0; |
161 | } |
162 | } |
163 | |
164 | export component IconButton { |
165 | in property <bool> enabled <=> base.enabled; |
166 | |
167 | in property <image> icon <=> base.icon; |
168 | |
169 | out property <bool> has-focus: base.has-focus; |
170 | out property <bool> pressed: self.enabled && base.pressed; |
171 | |
172 | callback clicked <=> base.clicked; |
173 | |
174 | min-height: max(40px, base.min-height); |
175 | min-width: max(40px, base.min-width); |
176 | forward-focus: base; |
177 | |
178 | accessible-role: button; |
179 | accessible-action-default => { |
180 | clicked(); |
181 | } |
182 | |
183 | base := MaterialButtonBase { |
184 | layout-padding-left: 8px; |
185 | layout-padding-right: 8px; |
186 | width: 100%; |
187 | height: 100%; |
188 | border-radius: 20px; |
189 | text-color: MaterialPalette.control-foreground-variant; |
190 | colorize-icon: true; |
191 | text-opacity: 1.0; |
192 | } |
193 | } |
194 | |