winio_ui_gtk/ui/
combo_box.rs1use std::rc::Rc;
2
3use gtk4::{
4 glib::{self, object::Cast},
5 prelude::ListModelExt,
6 subclass::prelude::ObjectSubclassExt,
7};
8use inherit_methods_macro::inherit_methods;
9use winio_callback::Callback;
10use winio_handle::AsContainer;
11use winio_primitive::{Point, Size};
12
13use crate::{Error, GlobalRuntime, Result, ui::Widget};
14
15#[derive(Debug)]
16pub struct ComboBox {
17 on_changed: Rc<Callback<()>>,
18 model: StringListModel,
19 widget: gtk4::DropDown,
20 handle: Widget,
21}
22
23#[inherit_methods(from = "self.handle")]
24impl ComboBox {
25 pub fn new(parent: impl AsContainer) -> Result<Self> {
26 let model = StringListModel::new();
27 let widget = gtk4::DropDown::new(Some(model.clone()), gtk4::Expression::NONE);
28 let handle = Widget::new(parent, unsafe { widget.clone().unsafe_cast() })?;
29 let on_changed = Rc::new(Callback::new());
30 widget.connect_selected_notify({
31 let on_changed = on_changed.clone();
32 move |_| {
33 on_changed.signal::<GlobalRuntime>(());
34 }
35 });
36 Ok(Self {
37 on_changed,
38 model,
39 widget,
40 handle,
41 })
42 }
43
44 pub fn is_visible(&self) -> Result<bool>;
45
46 pub fn set_visible(&mut self, v: bool) -> Result<()>;
47
48 pub fn is_enabled(&self) -> Result<bool>;
49
50 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
51
52 pub fn preferred_size(&self) -> Result<Size>;
53
54 pub fn loc(&self) -> Result<Point>;
55
56 pub fn set_loc(&mut self, p: Point) -> Result<()>;
57
58 pub fn size(&self) -> Result<Size>;
59
60 pub fn set_size(&mut self, s: Size) -> Result<()>;
61
62 pub fn tooltip(&self) -> Result<String>;
63
64 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
65
66 pub fn text(&self) -> Result<String> {
67 match self.widget.selected_item() {
68 Some(obj) => Ok(obj
69 .downcast::<gtk4::StringObject>()
70 .map_err(|_| Error::CastFailed)?
71 .string()
72 .to_string()),
73 None => Ok(String::new()),
74 }
75 }
76
77 pub fn set_text(&mut self, _s: impl AsRef<str>) -> Result<()> {
78 self.handle.reset_preferred_size();
79 Ok(())
80 }
81
82 pub fn selection(&self) -> Result<Option<usize>> {
83 let index = self.widget.selected();
84 if index == gtk4::INVALID_LIST_POSITION {
85 Ok(None)
86 } else {
87 Ok(Some(index as _))
88 }
89 }
90
91 pub fn set_selection(&mut self, i: usize) -> Result<()> {
92 self.widget.set_selected(i as _);
93 Ok(())
94 }
95
96 pub fn is_editable(&self) -> Result<bool> {
97 Ok(self.widget.enables_search())
98 }
99
100 pub fn set_editable(&mut self, v: bool) -> Result<()> {
101 self.widget.set_enable_search(v);
102 Ok(())
103 }
104
105 pub async fn wait_change(&self) {
106 self.on_changed.wait().await
107 }
108
109 pub async fn wait_select(&self) {
110 self.on_changed.wait().await
111 }
112
113 pub fn len(&self) -> Result<usize> {
114 Ok(self.model.n_items() as _)
115 }
116
117 pub fn is_empty(&self) -> Result<bool> {
118 Ok(self.len()? == 0)
119 }
120
121 pub fn clear(&mut self) -> Result<()> {
122 self.model.clear();
123 self.handle.reset_preferred_size();
124 Ok(())
125 }
126
127 pub fn get(&self, i: usize) -> Result<String> {
128 self.model.get(i)
129 }
130
131 pub fn set(&mut self, i: usize, s: impl AsRef<str>) -> Result<()> {
132 self.model.set(i, s.as_ref().to_string())?;
133 self.handle.reset_preferred_size();
134 Ok(())
135 }
136
137 pub fn insert(&mut self, i: usize, s: impl AsRef<str>) -> Result<()> {
138 self.model.insert(i, s.as_ref().to_string());
139 self.handle.reset_preferred_size();
140 Ok(())
141 }
142
143 pub fn remove(&mut self, i: usize) -> Result<()> {
144 self.model.remove(i as _)?;
145 self.handle.reset_preferred_size();
146 Ok(())
147 }
148}
149
150winio_handle::impl_as_widget!(ComboBox, handle);
151
152mod imp {
153 use std::cell::RefCell;
154
155 use gtk4::{
156 glib,
157 prelude::{Cast, ListModelExt},
158 subclass::prelude::*,
159 };
160
161 #[derive(Debug, Default)]
162 pub struct StringListModel {
163 strings: RefCell<Vec<String>>,
164 }
165
166 #[glib::object_subclass]
167 impl ObjectSubclass for StringListModel {
168 type Interfaces = (gtk4::gio::ListModel,);
169 type ParentType = glib::Object;
170 type Type = super::StringListModel;
171
172 const ABSTRACT: bool = false;
173 const NAME: &'static str = "StringListModel";
174 }
175
176 impl ObjectImpl for StringListModel {}
177
178 impl ListModelImpl for StringListModel {
179 fn item_type(&self) -> glib::Type {
180 glib::Type::OBJECT
181 }
182
183 fn n_items(&self) -> u32 {
184 self.strings.borrow().len() as _
185 }
186
187 fn item(&self, position: u32) -> Option<glib::Object> {
188 if position >= self.n_items() {
189 return None;
190 }
191 Some(gtk4::StringObject::new(&self.strings.borrow()[position as usize]).upcast())
192 }
193 }
194
195 impl StringListModel {
196 pub fn insert(&self, i: usize, s: String) {
197 self.strings.borrow_mut().insert(i, s);
198 self.obj().items_changed(i as _, 0, 1);
199 }
200
201 pub fn remove(&self, i: usize) -> Option<()> {
202 let mut strings = self.strings.borrow_mut();
203 if i >= strings.len() {
204 return None;
205 }
206 strings.remove(i);
207 self.obj().items_changed(i as _, 1, 0);
208 Some(())
209 }
210
211 pub fn get(&self, i: usize) -> Option<String> {
212 self.strings.borrow().get(i).cloned()
213 }
214
215 pub fn set(&self, i: usize, s: String) -> Option<()> {
216 *self.strings.borrow_mut().get_mut(i)? = s;
217 self.obj().items_changed(i as _, 1, 1);
218 Some(())
219 }
220
221 pub fn clear(&self) {
222 let len = {
223 let mut strings = self.strings.borrow_mut();
224 let len = strings.len();
225 strings.clear();
226 len
227 };
228 self.obj().items_changed(0, len as _, 0);
229 }
230 }
231}
232
233glib::wrapper! {
234 pub struct StringListModel(ObjectSubclass<imp::StringListModel>)
235 @implements gtk4::gio::ListModel;
236}
237
238impl Default for StringListModel {
239 fn default() -> Self {
240 Self::new()
241 }
242}
243
244impl StringListModel {
245 pub fn new() -> Self {
246 glib::Object::new()
247 }
248
249 pub fn clear(&self) {
250 imp::StringListModel::from_obj(self).clear();
251 }
252
253 pub fn get(&self, i: usize) -> Result<String> {
254 imp::StringListModel::from_obj(self)
255 .get(i)
256 .ok_or(Error::Index(i))
257 }
258
259 pub fn set(&self, i: usize, s: String) -> Result<()> {
260 imp::StringListModel::from_obj(self)
261 .set(i, s)
262 .ok_or(Error::Index(i))
263 }
264
265 pub fn insert(&self, i: usize, s: String) {
266 imp::StringListModel::from_obj(self).insert(i, s);
267 }
268
269 pub fn remove(&self, i: usize) -> Result<()> {
270 imp::StringListModel::from_obj(self)
271 .remove(i)
272 .ok_or(Error::Index(i))
273 }
274}