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 { CosmicFontSettings, CosmicPalette, Icons, CosmicSizeSettings } from "styling.slint";
5import { MenuBorder, ListItem, StateLayerBase } 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 <length> popup-padding: 4px;
19
20 property <int> visible-items: min(6, model.length);
21
22 min-width: max(160px, layout.min-height);
23 min-height: max(32px, 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 opacity: 0.5;
37 }
38 ]
39
40 base := ComboBoxBase {
41 width: 100%;
42 height: 100%;
43
44 show-popup => {
45 popup.show();
46 }
47 close-popup => {
48 popup.close();
49 }
50 }
51
52 background := Rectangle {
53 border-radius: 16px;
54 background: CosmicPalette.control-background;
55 border-width: 1px;
56 border-color: CosmicPalette.border;
57
58 layout := HorizontalLayout {
59 padding-left: 16px;
60 padding-right: 16px;
61 spacing: 10px;
62
63 text := Text {
64 horizontal-alignment: left;
65 vertical-alignment: center;
66 font-size: CosmicFontSettings.body.font-size;
67 font-weight: CosmicFontSettings.body.font-weight;
68 color: CosmicPalette.control-foreground;
69 text: root.current-value;
70 accessible-role: none;
71 }
72
73 Image {
74 y: (parent.height - self.height) / 2;
75 width: 16px;
76 height: 16px;
77 colorize: CosmicPalette.control-foreground;
78 source: Icons.dropdown;
79 accessible-role: none;
80 }
81 }
82
83 StateLayerBase {
84 width: 100%;
85 height: 100%;
86 border-radius: background.border-radius;
87 pressed: base.pressed;
88 has-focus: base.has-focus;
89 has-hover: base.has-hover;
90 enabled: root.enabled;
91 }
92 }
93
94 popup := PopupWindow {
95 x: 0;
96 // Position the popup so that the first element is over the popup.
97 // Ideally it should be so that the current element is over the popup.
98 y: root.height + 4px;
99 width: root.width;
100 height: root.visible-items * CosmicSizeSettings.item-height + 2 * root.popup-padding;
101 forward-focus: inner-fs;
102
103 inner-fs := FocusScope {
104 focus-changed-event => {
105 base.popup-has-focus = self.has-focus;
106 }
107 key-pressed(event) => {
108 return base.popup-key-handler(event);
109 }
110
111 MenuBorder {
112 ScrollView {
113 VerticalLayout {
114 alignment: start;
115 padding: root.popup-padding;
116
117 for value[index] in root.model : ListItem {
118 item: { text: value };
119 is-selected: index == root.current-index;
120 has-hover: touch-area.has-hover;
121 pressed: touch-area.pressed;
122
123 touch-area := TouchArea {
124 clicked => {
125 base.select(index);
126 }
127 }
128 }
129 }
130 }
131 }
132 }
133 }
134}
135