termux_gui/components/
layout.rs

1//! Layout components
2
3use serde_json::json;
4use crate::activity::Activity;
5use crate::view::View;
6use crate::error::Result;
7
8/// A LinearLayout arranges views linearly
9pub struct LinearLayout {
10    view: View,
11    #[allow(dead_code)]
12    aid: i64,
13}
14
15impl LinearLayout {
16    /// Create a new vertical LinearLayout
17    pub fn new(activity: &mut Activity, parent: Option<i64>) -> Result<Self> {
18        Self::new_with_orientation(activity, parent, true)
19    }
20    
21    /// Create a new LinearLayout with specified orientation
22    /// 
23    /// # Arguments
24    /// * `vertical` - If true, arranges children vertically; if false, horizontally
25    pub fn new_with_orientation(activity: &mut Activity, parent: Option<i64>, vertical: bool) -> Result<Self> {
26        let mut params = json!({
27            "aid": activity.id(),
28            "vertical": vertical
29        });
30        
31        // Only set parent if explicitly provided
32        if let Some(parent_id) = parent {
33            params["parent"] = json!(parent_id);
34        }
35        
36        let response = activity.send_read(&json!({
37            "method": "createLinearLayout",
38            "params": params
39        }))?;
40        
41        let id = response
42            .as_i64()
43            .ok_or_else(|| crate::error::GuiError::InvalidResponse("Invalid id".to_string()))?;
44        
45        Ok(LinearLayout {
46            view: View::new(id),
47            aid: activity.id(),
48        })
49    }
50    
51    /// Get the view ID
52    pub fn id(&self) -> i64 {
53        self.view.id()
54    }
55    
56    /// Get the underlying View
57    pub fn view(&self) -> &View {
58        &self.view
59    }
60}
61
62/// A NestedScrollView provides scrolling capability
63pub struct NestedScrollView {
64    view: View,
65    #[allow(dead_code)]
66    aid: i64,
67}
68
69impl NestedScrollView {
70    /// Create a new NestedScrollView
71    pub fn new(activity: &mut Activity, parent: Option<i64>) -> Result<Self> {
72        let mut params = json!({
73            "aid": activity.id(),
74            "nobar": false,
75            "snapping": false
76        });
77        
78        // Only set parent if explicitly provided
79        if let Some(parent_id) = parent {
80            params["parent"] = json!(parent_id);
81        }
82        
83        let response = activity.send_read(&json!({
84            "method": "createNestedScrollView",
85            "params": params
86        }))?;
87        
88        let id = response
89            .as_i64()
90            .ok_or_else(|| crate::error::GuiError::InvalidResponse("Invalid id".to_string()))?;
91        
92        Ok(NestedScrollView {
93            view: View::new(id),
94            aid: activity.id(),
95        })
96    }
97    
98    /// Get the view ID
99    pub fn id(&self) -> i64 {
100        self.view.id()
101    }
102    
103    /// Get the underlying View
104    pub fn view(&self) -> &View {
105        &self.view
106    }
107}
108
109/// A FrameLayout is a simple layout that stacks children on top of each other
110pub struct FrameLayout {
111    view: View,
112    #[allow(dead_code)]
113    aid: i64,
114}
115
116impl FrameLayout {
117    /// Create a new FrameLayout
118    /// 
119    /// Children are drawn in the order they are added, with the last child on top.
120    /// FrameLayout is useful for overlaying views or creating simple stacked layouts.
121    pub fn new(activity: &mut Activity, parent: Option<i64>) -> Result<Self> {
122        let mut params = json!({
123            "aid": activity.id()
124        });
125        
126        // Only set parent if explicitly provided
127        if let Some(parent_id) = parent {
128            params["parent"] = json!(parent_id);
129        }
130        
131        let response = activity.send_read(&json!({
132            "method": "createFrameLayout",
133            "params": params
134        }))?;
135        
136        let id = response
137            .as_i64()
138            .ok_or_else(|| crate::error::GuiError::InvalidResponse("Invalid id".to_string()))?;
139        
140        Ok(FrameLayout {
141            view: View::new(id),
142            aid: activity.id(),
143        })
144    }
145    
146    /// Get the view ID
147    pub fn id(&self) -> i64 {
148        self.view.id()
149    }
150    
151    /// Get the underlying View
152    pub fn view(&self) -> &View {
153        &self.view
154    }
155}
156
157/// A GridLayout arranges children in a grid
158pub struct GridLayout {
159    view: View,
160    #[allow(dead_code)]
161    aid: i64,
162    #[allow(dead_code)]
163    rows: i32,
164    #[allow(dead_code)]
165    cols: i32,
166}
167
168impl GridLayout {
169    /// Create a new GridLayout with specified rows and columns
170    /// 
171    /// # Arguments
172    /// * `rows` - Number of rows in the grid
173    /// * `cols` - Number of columns in the grid
174    pub fn new(activity: &mut Activity, rows: i32, cols: i32, parent: Option<i64>) -> Result<Self> {
175        let mut params = json!({
176            "aid": activity.id(),
177            "rows": rows,
178            "cols": cols
179        });
180        
181        // Only set parent if explicitly provided
182        if let Some(parent_id) = parent {
183            params["parent"] = json!(parent_id);
184        }
185        
186        let response = activity.send_read(&json!({
187            "method": "createGridLayout",
188            "params": params
189        }))?;
190        
191        let id = response
192            .as_i64()
193            .ok_or_else(|| crate::error::GuiError::InvalidResponse("Invalid id".to_string()))?;
194        
195        Ok(GridLayout {
196            view: View::new(id),
197            aid: activity.id(),
198            rows,
199            cols,
200        })
201    }
202    
203    /// Get the view ID
204    pub fn id(&self) -> i64 {
205        self.view.id()
206    }
207    
208    /// Get the underlying View
209    pub fn view(&self) -> &View {
210        &self.view
211    }
212}
213
214/// A HorizontalScrollView provides horizontal scrolling capability
215pub struct HorizontalScrollView {
216    view: View,
217    #[allow(dead_code)]
218    aid: i64,
219}
220
221impl HorizontalScrollView {
222    /// Create a new HorizontalScrollView
223    pub fn new(activity: &mut Activity, parent: Option<i64>) -> Result<Self> {
224        let mut params = json!({
225            "aid": activity.id(),
226            "nobar": false,
227            "snapping": false,
228            "fillviewport": true  // 改为true,让子视图填充viewport
229        });
230        
231        // Only set parent if explicitly provided
232        if let Some(parent_id) = parent {
233            params["parent"] = json!(parent_id);
234        }
235        
236        let response = activity.send_read(&json!({
237            "method": "createHorizontalScrollView",
238            "params": params
239        }))?;
240        
241        let id = response
242            .as_i64()
243            .ok_or_else(|| crate::error::GuiError::InvalidResponse("Invalid id".to_string()))?;
244        
245        Ok(HorizontalScrollView {
246            view: View::new(id),
247            aid: activity.id(),
248        })
249    }
250    
251    /// Get the view ID
252    pub fn id(&self) -> i64 {
253        self.view.id()
254    }
255    
256    /// Get the underlying View
257    pub fn view(&self) -> &View {
258        &self.view
259    }
260}
261
262/// A SwipeRefreshLayout provides pull-to-refresh functionality
263pub struct SwipeRefreshLayout {
264    view: View,
265    aid: i64,
266}
267
268impl SwipeRefreshLayout {
269    /// Create a new SwipeRefreshLayout
270    pub fn new(activity: &mut Activity, parent: Option<i64>) -> Result<Self> {
271        let mut params = json!({
272            "aid": activity.id()
273        });
274        
275        // Only set parent if explicitly provided
276        if let Some(parent_id) = parent {
277            params["parent"] = json!(parent_id);
278        }
279        
280        let response = activity.send_read(&json!({
281            "method": "createSwipeRefreshLayout",
282            "params": params
283        }))?;
284        
285        let id = response
286            .as_i64()
287            .ok_or_else(|| crate::error::GuiError::InvalidResponse("Invalid id".to_string()))?;
288        
289        Ok(SwipeRefreshLayout {
290            view: View::new(id),
291            aid: activity.id(),
292        })
293    }
294    
295    /// Get the view ID
296    pub fn id(&self) -> i64 {
297        self.view.id()
298    }
299    
300    /// Get the underlying View
301    pub fn view(&self) -> &View {
302        &self.view
303    }
304    
305    /// Set whether the refresh animation is showing
306    /// 
307    /// Call with false after refresh is complete to stop the animation
308    pub fn set_refreshing(&self, activity: &mut Activity, refreshing: bool) -> Result<()> {
309        activity.send(&json!({
310            "method": "setRefreshing",
311            "params": {
312                "aid": self.aid,
313                "id": self.view.id(),
314                "refresh": refreshing
315            }
316        }))?;
317        Ok(())
318    }
319}
320
321/// A TabLayout displays a horizontal row of tabs
322/// 
323/// TabLayout is useful for creating tabbed interfaces. It emits 'itemselected' 
324/// events when a tab is clicked, with the tab index as the value.
325pub struct TabLayout {
326    view: View,
327    aid: i64,
328}
329
330impl TabLayout {
331    /// Create a new TabLayout
332    pub fn new(activity: &mut Activity, parent: Option<i64>) -> Result<Self> {
333        let mut params = json!({
334            "aid": activity.id()
335        });
336        
337        // Only set parent if explicitly provided
338        if let Some(parent_id) = parent {
339            params["parent"] = json!(parent_id);
340        }
341        
342        let response = activity.send_read(&json!({
343            "method": "createTabLayout",
344            "params": params
345        }))?;
346        
347        let id = response
348            .as_i64()
349            .ok_or_else(|| crate::error::GuiError::InvalidResponse("Invalid id".to_string()))?;
350        
351        Ok(TabLayout {
352            view: View::new(id),
353            aid: activity.id(),
354        })
355    }
356    
357    /// Get the view ID
358    pub fn id(&self) -> i64 {
359        self.view.id()
360    }
361    
362    /// Get the underlying View
363    pub fn view(&self) -> &View {
364        &self.view
365    }
366    
367    /// Set the list of tab labels
368    /// 
369    /// # Arguments
370    /// * `tabs` - A slice of strings representing the tab labels
371    /// 
372    /// # Example
373    /// ```no_run
374    /// tab_layout.set_list(activity, &["Page 1", "Page 2", "Page 3"])?;
375    /// ```
376    pub fn set_list(&self, activity: &mut Activity, tabs: &[&str]) -> Result<()> {
377        activity.send(&json!({
378            "method": "setList",
379            "params": {
380                "aid": self.aid,
381                "id": self.view.id(),
382                "list": tabs
383            }
384        }))?;
385        Ok(())
386    }
387    
388    /// Programmatically select a tab
389    /// 
390    /// # Arguments
391    /// * `index` - The zero-based index of the tab to select
392    /// 
393    /// # Example
394    /// ```no_run
395    /// // Select the second tab (index 1)
396    /// tab_layout.select_tab(activity, 1)?;
397    /// ```
398    pub fn select_tab(&self, activity: &mut Activity, index: usize) -> Result<()> {
399        activity.send(&json!({
400            "method": "selectTab",
401            "params": {
402                "aid": self.aid,
403                "id": self.view.id(),
404                "tab": index
405            }
406        }))?;
407        Ok(())
408    }
409}