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 { CupertinoFontSettings, CupertinoPalette, Icons, CupertinoSizeSettings } from "styling.slint";
5import { MenuBorder, ListItem, FocusBorder } from "components.slint";
6import { ComboBoxBase } from "../common/combobox-base.slint";
7import { ScrollView } from "./scrollview.slint";
8
9export component ComboBox {
10 in property <[string]> model <=> base.model;
11 in property <bool> enabled <=> base.enabled;
12 out property <bool> has-focus <=> base.has-focus;
13 in-out property <int> current-index <=> base.current-index;
14 in-out property <string> current-value <=> base.current-value;
15
16 callback selected <=> base.selected;
17
18 property <brush> background: CupertinoPalette.control-background;
19 property <length> popup-padding: 4px;
20 property <int> visible-items: min(6, model.length);
21
22 min-width: max(160px, layout.min-width);
23 min-height: max(22px, layout.min-height);
24 horizontal-stretch: 1;
25 vertical-stretch: 0;
26 forward-focus: base;
27 accessible-role: combobox;
28 accessible-enabled: root.enabled;
29 accessible-expandable: true;
30 accessible-expanded: base.popup-has-focus;
31 accessible-value <=> root.current-value;
32 accessible-action-expand => { base.show-popup(); }
33
34 states [
35 disabled when !root.enabled : {
36 text.color: CupertinoPalette.foreground-secondary;
37 top-icon.colorize: CupertinoPalette.foreground-secondary;
38 bottom-icon.colorize: CupertinoPalette.foreground-secondary;
39 root.background: CupertinoPalette.tertiary-control-background;
40 }
41 pressed when base.pressed : {
42 root.background: CupertinoPalette.secondary-control-background;
43 }
44 ]
45
46 base := ComboBoxBase {
47 width: 100%;
48 height: 100%;
49
50 // Mac doesn't react on mouse wheel on the ComboBox.
51 scroll-delta: 1000000px;
52
53 show-popup => {
54 popup.show();
55 }
56 close-popup => {
57 popup.close();
58 }
59 }
60
61 FocusBorder {
62 x: (parent.width - self.width) / 2;
63 y: (parent.height - self.height) / 2;
64 width: parent.width + 6px;
65 height: parent.height + 6px;
66 border-radius: 8px;
67 has-focus: root.has-focus;
68 }
69
70 Rectangle {
71 drop-shadow-blur: 0.25px;
72 drop-shadow-color: #00000066;
73 drop-shadow-offset-y: 0.25px;
74 border-radius: 5px;
75 background: root.background;
76
77 Rectangle {
78 drop-shadow-blur: 1px;
79 drop-shadow-color: #00000026;
80 drop-shadow-offset-y: 1px;
81 border-radius: parent.border-radius;
82 background: root.background;
83 border-width: 1px;
84 border-color: CupertinoPalette.decent-border;
85 opacity: root.enabled ? 1 : 0.5;
86 }
87 }
88
89 layout := HorizontalLayout {
90 y: (parent.height - self.height) / 2;
91 padding-left: 8px;
92 padding-right: 8px;
93 padding-top: 4px;
94 padding-bottom: 4px;
95 spacing: 4px;
96
97 text := Text {
98 horizontal-stretch: 1;
99 horizontal-alignment: left;
100 vertical-alignment: center;
101 font-size: CupertinoFontSettings.body.font-size;
102 font-weight: CupertinoFontSettings.body.font-weight;
103 color: CupertinoPalette.foreground;
104 text: root.current-value;
105 accessible-role: none;
106 }
107
108 VerticalLayout {
109 alignment: center;
110
111 Rectangle {
112 horizontal-stretch: 0;
113 min-width: 16px;
114 min-height: max(self.min-width, button-layout.min-height);
115
116 if root.enabled : Rectangle {
117 width: 100%;
118 height: 100%;
119 drop-shadow-blur: 3px;
120 drop-shadow-color: #00000066;
121 drop-shadow-offset-y: 0.5px;
122 border-radius: 4px;
123 background: CupertinoPalette.accent-background;
124
125 Rectangle {
126 drop-shadow-blur: 2px;
127 drop-shadow-color: #00000026;
128 drop-shadow-offset-y: 1px;
129 border-radius: parent.border-radius;
130 background: parent.background;
131 }
132
133 Rectangle {
134 drop-shadow-blur: 1px;
135 drop-shadow-color: #00000026;
136 drop-shadow-offset-y: 0.5px;
137 border-radius: parent.border-radius;
138 background: parent.background;
139 }
140
141 Rectangle {
142 border-radius: parent.border-radius;
143 background: CupertinoPalette.dimmer;
144 opacity: 0.17;
145 }
146 }
147
148 button-layout := VerticalLayout {
149 padding: 4px;
150 spacing: 4px;
151
152 top-icon := Image {
153 x: (parent.width - self.width) / 2;
154 colorize: CupertinoPalette.accent-foreground;
155 source: Icons.chevron-up;
156 accessible-role: none;
157 }
158
159 bottom-icon := Image {
160 x: (parent.width - self.width) / 2;
161 colorize: CupertinoPalette.accent-foreground;
162 source: Icons.chevron-down;
163 accessible-role: none;
164 }
165 }
166 }
167 }
168 }
169
170 popup := PopupWindow {
171 x: 0;
172 y: parent.height + 6px;
173 width: root.width;
174 height: root.visible-items * CupertinoSizeSettings.item-height + 2 * root.popup-padding;
175 forward-focus: inner-fs;
176
177 inner-fs := FocusScope {
178 focus-changed-event => {
179 base.popup-has-focus = self.has-focus;
180 }
181 key-pressed(event) => {
182 return base.popup-key-handler(event);
183 }
184
185 MenuBorder {
186 ScrollView {
187 VerticalLayout {
188 alignment: start;
189 padding: root.popup-padding;
190
191 for value[index] in root.model : ListItem {
192 padding-horizontal: 0;
193 item: { text: value };
194 is-selected: index == root.current-index;
195 has-hover: touch-area.has-hover;
196 pressed: touch-area.pressed;
197 pressed-x: touch-area.pressed-x;
198 pressed-y: touch-area.pressed-y;
199
200 touch-area := TouchArea {
201 clicked => {
202 base.select(index);
203 }
204 }
205 }
206 }
207 }
208 }
209 }
210 }
211}
212