termux_gui/
view.rs

1//! Base View type and common view operations
2//!
3//! ## Important Notes on Dimensions and Units
4//!
5//! Android uses two different units for View dimensions:
6//! - **dp (density-independent pixels)**: Default unit, scales with screen density
7//! - **px (pixels)**: Actual screen pixels
8//!
9//! ### Critical for TabLayout/ScrollView
10//!
11//! When working with `HorizontalScrollView` or `TabLayout`:
12//! 1. `get_dimensions()` returns values in **pixels (px)**
13//! 2. `set_width()` and `set_height()` use **dp** by default
14//! 3. **You must use `set_width_px()` and `set_height_px()`** to match dimensions!
15//!
16//! ### Example
17//!
18//! ```rust,no_run
19//! # use termux_gui::{Activity, Result};
20//! # fn example(activity: &mut Activity, scroll_view_id: i64, page_id: i64) -> Result<()> {
21//! // Get dimensions (returns pixels)
22//! let (width_px, height_px) = scroll_view.view().get_dimensions(activity)?;
23//!
24//! // WRONG: This uses dp, not px!
25//! // page.view().set_width(activity, width_px)?;  // Width mismatch!
26//!
27//! // CORRECT: Use set_width_px to match pixel values
28//! page.view().set_width_px(activity, width_px)?;  // Perfect match!
29//! # Ok(())
30//! # }
31//! ```
32
33use serde_json::json;
34use crate::activity::Activity;
35use crate::error::Result;
36
37/// Special dimension constants for Android layouts
38pub const MATCH_PARENT: i32 = -1;
39pub const WRAP_CONTENT: i32 = -2;
40
41/// Base View structure
42pub struct View {
43    id: i64,
44}
45
46impl View {
47    /// Create a new View with the given ID
48    pub fn new(id: i64) -> Self {
49        View { id }
50    }
51    
52    /// Get the view ID
53    pub fn id(&self) -> i64 {
54        self.id
55    }
56    
57    /// Set view width in dp (density-independent pixels)
58    /// 
59    /// **Note**: If you need to match dimensions from `get_dimensions()`,
60    /// use `set_width_px()` instead, as `get_dimensions()` returns pixels.
61    pub fn set_width(&self, activity: &mut Activity, width: i32) -> Result<()> {
62        activity.send(&json!({
63            "method": "setWidth",
64            "params": {
65                "aid": activity.id(),
66                "id": self.id,
67                "width": width
68            }
69        }))?;
70        Ok(())
71    }
72    
73    /// Set view width in pixels
74    /// 
75    /// **Use this when matching dimensions from `get_dimensions()`**
76    /// which returns pixel values, not dp values.
77    pub fn set_width_px(&self, activity: &mut Activity, width: i32) -> Result<()> {
78        activity.send(&json!({
79            "method": "setWidth",
80            "params": {
81                "aid": activity.id(),
82                "id": self.id,
83                "width": width,
84                "px": true
85            }
86        }))?;
87        Ok(())
88    }
89    
90    /// Set view height in dp (density-independent pixels)
91    /// 
92    /// **Note**: If you need to match dimensions from `get_dimensions()`,
93    /// use `set_height_px()` instead, as `get_dimensions()` returns pixels.
94    pub fn set_height(&self, activity: &mut Activity, height: i32) -> Result<()> {
95        activity.send(&json!({
96            "method": "setHeight",
97            "params": {
98                "aid": activity.id(),
99                "id": self.id,
100                "height": height
101            }
102        }))?;
103        Ok(())
104    }
105    
106    /// Set view height in pixels
107    /// 
108    /// **Use this when matching dimensions from `get_dimensions()`**
109    /// which returns pixel values, not dp values.
110    pub fn set_height_px(&self, activity: &mut Activity, height: i32) -> Result<()> {
111        activity.send(&json!({
112            "method": "setHeight",
113            "params": {
114                "aid": activity.id(),
115                "id": self.id,
116                "height": height,
117                "px": true
118            }
119        }))?;
120        Ok(())
121    }
122    
123    /// Set view width and height
124    pub fn set_dimensions(&self, activity: &mut Activity, width: i32, height: i32) -> Result<()> {
125        // Call both methods since there's no combined setDimensions
126        self.set_width(activity, width)?;
127        self.set_height(activity, height)?;
128        Ok(())
129    }
130    
131    /// Set view margin
132    pub fn set_margin(&self, activity: &mut Activity, margin: i32) -> Result<()> {
133        activity.send(&json!({
134            "method": "setMargin",
135            "params": {
136                "aid": activity.id(),
137                "id": self.id,
138                "margin": margin
139            }
140        }))?;
141        Ok(())
142    }
143    
144    /// Set view width to WRAP_CONTENT
145    pub fn set_width_wrap_content(&self, activity: &mut Activity) -> Result<()> {
146        self.set_width(activity, WRAP_CONTENT)
147    }
148    
149    /// Set view height to WRAP_CONTENT
150    pub fn set_height_wrap_content(&self, activity: &mut Activity) -> Result<()> {
151        self.set_height(activity, WRAP_CONTENT)
152    }
153    
154    /// Set view width to MATCH_PARENT
155    pub fn set_width_match_parent(&self, activity: &mut Activity) -> Result<()> {
156        self.set_width(activity, MATCH_PARENT)
157    }
158    
159    /// Set view height to MATCH_PARENT
160    pub fn set_height_match_parent(&self, activity: &mut Activity) -> Result<()> {
161        self.set_height(activity, MATCH_PARENT)
162    }
163    
164    /// Set LinearLayout parameters for this view
165    /// 
166    /// # Arguments
167    /// * `activity` - The activity
168    /// * `weight` - Layout weight (higher weight = more space)
169    /// * `position` - Optional position index in the layout
170    pub fn set_linear_layout_params(&self, activity: &mut Activity, weight: i32, position: Option<i32>) -> Result<()> {
171        let mut params = json!({
172            "aid": activity.id(),
173            "id": self.id,
174            "weight": weight
175        });
176        
177        if let Some(pos) = position {
178            params["position"] = json!(pos);
179        }
180        
181        activity.send(&json!({
182            "method": "setLinearLayoutParams",
183            "params": params
184        }))?;
185        Ok(())
186    }
187    
188    /// Set GridLayout parameters for this view
189    /// 
190    /// # Arguments
191    /// * `row` - Row index (0-based)
192    /// * `col` - Column index (0-based)
193    /// * `row_size` - Number of rows this view spans (default 1)
194    /// * `col_size` - Number of columns this view spans (default 1)
195    /// * `alignment_row` - Vertical alignment: "top", "bottom", "center", "baseline", "fill"
196    /// * `alignment_col` - Horizontal alignment: "left", "right", "center", "fill"
197    pub fn set_grid_layout_params(&self, activity: &mut Activity, 
198                                   row: i32, col: i32,
199                                   row_size: i32, col_size: i32,
200                                   alignment_row: &str, alignment_col: &str) -> Result<()> {
201        activity.send(&json!({
202            "method": "setGridLayoutParams",
203            "params": {
204                "aid": activity.id(),
205                "id": self.id,
206                "row": row,
207                "col": col,
208                "rowsize": row_size,
209                "colsize": col_size,
210                "alignmentrow": alignment_row,
211                "alignmentcol": alignment_col
212            }
213        }))?;
214        Ok(())
215    }
216    
217    /// Get the dimensions (width, height) of this view in pixels
218    /// Returns (width, height)
219    pub fn get_dimensions(&self, activity: &mut Activity) -> Result<(i32, i32)> {
220        let response = activity.send_read(&json!({
221            "method": "getDimensions",
222            "params": {
223                "aid": activity.id(),
224                "id": self.id
225            }
226        }))?;
227        
228        // Response is an array [width, height]
229        if let Some(arr) = response.as_array() {
230            let width = arr.get(0).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
231            let height = arr.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
232            Ok((width, height))
233        } else {
234            Ok((0, 0))
235        }
236    }
237    
238    /// Set the background color of this view
239    /// 
240    /// Color format: 0xAARRGGBB (alpha, red, green, blue in hexadecimal)
241    /// 
242    /// # Examples
243    /// 
244    /// ```rust,no_run
245    /// # use termux_gui::{Activity, Result};
246    /// # fn example(activity: &mut Activity, view_id: i64) -> Result<()> {
247    /// // Solid red
248    /// view.set_background_color(activity, 0xFFFF0000u32 as i32)?;
249    /// 
250    /// // Semi-transparent blue
251    /// view.set_background_color(activity, 0x800000FFu32 as i32)?;
252    /// 
253    /// // White
254    /// view.set_background_color(activity, 0xFFFFFFFFu32 as i32)?;
255    /// # Ok(())
256    /// # }
257    /// ```
258    pub fn set_background_color(&self, activity: &mut Activity, color: i32) -> Result<()> {
259        activity.send(&json!({
260            "method": "setBackgroundColor",
261            "params": {
262                "aid": activity.id(),
263                "id": self.id,
264                "color": color
265            }
266        }))?;
267        Ok(())
268    }
269}