Skip to main content

winio_ui_gtk/ui/
combo_box.rs

1use 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}