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
4import { StateLayer } from "components.slint";
5import { MaterialFontSettings, MaterialPalette, Elevation } from "styling.slint";
6
7component 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.
68export 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
134export 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
164export 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